├── .gitattributes ├── .github └── workflows │ └── github-releases-to-discord.yml ├── .gitignore ├── LICENSE ├── README.md ├── Siege_installation_instructions.md └── SingleplayerLauncher ├── .editorconfig ├── Api ├── GameController.cs ├── JoinGameRequestDto.cs └── LaunchGameRequestDto.cs ├── Configuration ├── BaseLoadouts.cs ├── GameConfig.cs ├── IConfiguration.cs ├── Settings.cs ├── SiegeLoadouts.cs └── SurvivalLoadouts.cs ├── Entities ├── Battlegrounds │ ├── Endless.cs │ ├── IBattleground.cs │ ├── Prologue.cs │ ├── Siege.cs │ ├── Survival.cs │ └── WeeklyChallenge.cs ├── BotDifficulty.cs ├── BotLoadout.cs ├── Difficulty.cs ├── Dye.cs ├── GameMode.cs ├── Hero.cs ├── Loadout │ ├── Archetype.cs │ ├── Consumable.cs │ ├── Gear.cs │ ├── Glyph.cs │ ├── Guardian.cs │ ├── SiegeRole.cs │ ├── SiegeTrait.cs │ ├── SurvivalTrait.cs │ ├── Trait.cs │ ├── Trap.cs │ ├── TrapPart.cs │ └── Wave.cs ├── Map.cs ├── Names │ ├── BotDifficulty.cs │ ├── Consumable.cs │ ├── Difficulty.cs │ ├── Dye.cs │ ├── GameMode.cs │ ├── Gear.cs │ ├── Glyph.cs │ ├── Guardian.cs │ ├── Hero.cs │ ├── Map.cs │ ├── Mod.cs │ ├── SiegeRole.cs │ ├── Skin.cs │ ├── Trait.cs │ ├── Trap.cs │ ├── TrapPart.cs │ ├── TrapPartSlot.cs │ ├── Wave.cs │ └── WeeklyChallenge.cs └── Skin.cs ├── Forms ├── ComboBoxHelper.cs ├── FolderPicker.cs ├── InputValidator.cs ├── LauncherMainForm.Designer.cs ├── LauncherMainForm.cs └── LauncherMainForm.resx ├── GameFiles ├── Config │ ├── CharacterData.cs │ ├── ConfigFile.cs │ └── DefaultGame.cs ├── Cooked │ ├── PawnWeaponUPK.cs │ ├── SpitfireGameUPK.cs │ ├── UPKFile.cs │ └── UPKHeroObjectContent.cs └── FileUtils.cs ├── GameLauncher.cs ├── Model ├── BaseLoadout.cs ├── Battleground.cs ├── GameInfo.cs ├── Language.cs ├── LoadoutItem.cs ├── SiegeLoadout.cs ├── SlotItem.cs └── SurvivalLoadout.cs ├── Mods ├── AccountLevelOverride.cs ├── AdditionalheroWeapon.cs ├── EnhancedTrapRotation.cs ├── GodMode.cs ├── Hardcore.cs ├── InvincibleBarricades.cs ├── Mod.cs ├── Mods.cs ├── NoLimitUniqueTraps.cs ├── NoTrapCap.cs ├── NoTrapGrid.cs ├── SellTrapsAnytime.cs ├── ShowTrapDamageFlyoffs.cs ├── StartingCoinOverride.cs ├── TrapTierOverride.cs ├── TrapsAnySurface.cs └── TrapsInTraps.cs ├── Program.cs ├── ProjectRechained.sln ├── ProjectRechainedLauncher.csproj ├── Properties ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── Utils ├── Base62Converter.cs ├── Comment.cs ├── Entry.cs ├── IniElement.cs ├── IniFile.cs └── Section.cs ├── app.config └── omdu-U1_512.ico /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/github-releases-to-discord.yml: -------------------------------------------------------------------------------- 1 | on: 2 | release: 3 | types: [published] 4 | 5 | jobs: 6 | github-releases-to-discord: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | - name: Github Releases To Discord 12 | uses: SethCohen/github-releases-to-discord@v1.15.0 13 | with: 14 | webhook_url: ${{ secrets.WEBHOOK_URL }} 15 | color: "2105893" 16 | username: "Release Changelog" 17 | avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png" 18 | content: "||<@&874767553271038012>||" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | 33 | # Visual Studio 2015/2017 cache/options directory 34 | .vs/ 35 | # Uncomment if you have tasks that create the project's static files in wwwroot 36 | #wwwroot/ 37 | 38 | # Visual Studio 2017 auto generated files 39 | Generated\ Files/ 40 | 41 | # MSTest test Results 42 | [Tt]est[Rr]esult*/ 43 | [Bb]uild[Ll]og.* 44 | 45 | # NUnit 46 | *.VisualState.xml 47 | TestResult.xml 48 | nunit-*.xml 49 | 50 | # Build Results of an ATL Project 51 | [Dd]ebugPS/ 52 | [Rr]eleasePS/ 53 | dlldata.c 54 | 55 | # Benchmark Results 56 | BenchmarkDotNet.Artifacts/ 57 | 58 | # .NET Core 59 | project.lock.json 60 | project.fragment.lock.json 61 | artifacts/ 62 | 63 | # StyleCop 64 | StyleCopReport.xml 65 | 66 | # Files built by Visual Studio 67 | *_i.c 68 | *_p.c 69 | *_h.h 70 | *.ilk 71 | *.meta 72 | *.obj 73 | *.iobj 74 | *.pch 75 | *.pdb 76 | *.ipdb 77 | *.pgc 78 | *.pgd 79 | *.rsp 80 | *.sbr 81 | *.tlb 82 | *.tli 83 | *.tlh 84 | *.tmp 85 | *.tmp_proj 86 | *_wpftmp.csproj 87 | *.log 88 | *.vspscc 89 | *.vssscc 90 | .builds 91 | *.pidb 92 | *.svclog 93 | *.scc 94 | 95 | # Chutzpah Test files 96 | _Chutzpah* 97 | 98 | # Visual C++ cache files 99 | ipch/ 100 | *.aps 101 | *.ncb 102 | *.opendb 103 | *.opensdf 104 | *.sdf 105 | *.cachefile 106 | *.VC.db 107 | *.VC.VC.opendb 108 | 109 | # Visual Studio profiler 110 | *.psess 111 | *.vsp 112 | *.vspx 113 | *.sap 114 | 115 | # Visual Studio Trace Files 116 | *.e2e 117 | 118 | # TFS 2012 Local Workspace 119 | $tf/ 120 | 121 | # Guidance Automation Toolkit 122 | *.gpState 123 | 124 | # ReSharper is a .NET coding add-in 125 | _ReSharper*/ 126 | *.[Rr]e[Ss]harper 127 | *.DotSettings.user 128 | 129 | # JustCode is a .NET coding add-in 130 | .JustCode 131 | 132 | # TeamCity is a build add-in 133 | _TeamCity* 134 | 135 | # DotCover is a Code Coverage Tool 136 | *.dotCover 137 | 138 | # AxoCover is a Code Coverage Tool 139 | .axoCover/* 140 | !.axoCover/settings.json 141 | 142 | # Visual Studio code coverage results 143 | *.coverage 144 | *.coveragexml 145 | 146 | # NCrunch 147 | _NCrunch_* 148 | .*crunch*.local.xml 149 | nCrunchTemp_* 150 | 151 | # MightyMoose 152 | *.mm.* 153 | AutoTest.Net/ 154 | 155 | # Web workbench (sass) 156 | .sass-cache/ 157 | 158 | # Installshield output folder 159 | [Ee]xpress/ 160 | 161 | # DocProject is a documentation generator add-in 162 | DocProject/buildhelp/ 163 | DocProject/Help/*.HxT 164 | DocProject/Help/*.HxC 165 | DocProject/Help/*.hhc 166 | DocProject/Help/*.hhk 167 | DocProject/Help/*.hhp 168 | DocProject/Help/Html2 169 | DocProject/Help/html 170 | 171 | # Click-Once directory 172 | publish/ 173 | 174 | # Publish Web Output 175 | *.[Pp]ublish.xml 176 | *.azurePubxml 177 | # Note: Comment the next line if you want to checkin your web deploy settings, 178 | # but database connection strings (with potential passwords) will be unencrypted 179 | *.pubxml 180 | *.publishproj 181 | 182 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 183 | # checkin your Azure Web App publish settings, but sensitive information contained 184 | # in these scripts will be unencrypted 185 | PublishScripts/ 186 | 187 | # NuGet Packages 188 | *.nupkg 189 | # NuGet Symbol Packages 190 | *.snupkg 191 | # The packages folder can be ignored because of Package Restore 192 | **/[Pp]ackages/* 193 | # except build/, which is used as an MSBuild target. 194 | !**/[Pp]ackages/build/ 195 | # Uncomment if necessary however generally it will be regenerated when needed 196 | #!**/[Pp]ackages/repositories.config 197 | # NuGet v3's project.json files produces more ignorable files 198 | *.nuget.props 199 | *.nuget.targets 200 | 201 | # Microsoft Azure Build Output 202 | csx/ 203 | *.build.csdef 204 | 205 | # Microsoft Azure Emulator 206 | ecf/ 207 | rcf/ 208 | 209 | # Windows Store app package directories and files 210 | AppPackages/ 211 | BundleArtifacts/ 212 | Package.StoreAssociation.xml 213 | _pkginfo.txt 214 | *.appx 215 | *.appxbundle 216 | *.appxupload 217 | 218 | # Visual Studio cache files 219 | # files ending in .cache can be ignored 220 | *.[Cc]ache 221 | # but keep track of directories ending in .cache 222 | !?*.[Cc]ache/ 223 | 224 | # Others 225 | ClientBin/ 226 | ~$* 227 | *~ 228 | *.dbmdl 229 | *.dbproj.schemaview 230 | *.jfm 231 | *.pfx 232 | *.publishsettings 233 | orleans.codegen.cs 234 | 235 | # Including strong name files can present a security risk 236 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 237 | #*.snk 238 | 239 | # Since there are multiple workflows, uncomment next line to ignore bower_components 240 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 241 | #bower_components/ 242 | 243 | # RIA/Silverlight projects 244 | Generated_Code/ 245 | 246 | # Backup & report files from converting an old project file 247 | # to a newer Visual Studio version. Backup files are not needed, 248 | # because we have git ;-) 249 | _UpgradeReport_Files/ 250 | Backup*/ 251 | UpgradeLog*.XML 252 | UpgradeLog*.htm 253 | ServiceFabricBackup/ 254 | *.rptproj.bak 255 | 256 | # SQL Server files 257 | *.mdf 258 | *.ldf 259 | *.ndf 260 | 261 | # Business Intelligence projects 262 | *.rdl.data 263 | *.bim.layout 264 | *.bim_*.settings 265 | *.rptproj.rsuser 266 | *- [Bb]ackup.rdl 267 | *- [Bb]ackup ([0-9]).rdl 268 | *- [Bb]ackup ([0-9][0-9]).rdl 269 | 270 | # Microsoft Fakes 271 | FakesAssemblies/ 272 | 273 | # GhostDoc plugin setting file 274 | *.GhostDoc.xml 275 | 276 | # Node.js Tools for Visual Studio 277 | .ntvs_analysis.dat 278 | node_modules/ 279 | 280 | # Visual Studio 6 build log 281 | *.plg 282 | 283 | # Visual Studio 6 workspace options file 284 | *.opt 285 | 286 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 287 | *.vbw 288 | 289 | # Visual Studio LightSwitch build output 290 | **/*.HTMLClient/GeneratedArtifacts 291 | **/*.DesktopClient/GeneratedArtifacts 292 | **/*.DesktopClient/ModelManifest.xml 293 | **/*.Server/GeneratedArtifacts 294 | **/*.Server/ModelManifest.xml 295 | _Pvt_Extensions 296 | 297 | # Paket dependency manager 298 | .paket/paket.exe 299 | paket-files/ 300 | 301 | # FAKE - F# Make 302 | .fake/ 303 | 304 | # CodeRush personal settings 305 | .cr/personal 306 | 307 | # Python Tools for Visual Studio (PTVS) 308 | __pycache__/ 309 | *.pyc 310 | 311 | # Cake - Uncomment if you are using it 312 | # tools/** 313 | # !tools/packages.config 314 | 315 | # Tabs Studio 316 | *.tss 317 | 318 | # Telerik's JustMock configuration file 319 | *.jmconfig 320 | 321 | # BizTalk build output 322 | *.btp.cs 323 | *.btm.cs 324 | *.odx.cs 325 | *.xsd.cs 326 | 327 | # OpenCover UI analysis results 328 | OpenCover/ 329 | 330 | # Azure Stream Analytics local run output 331 | ASALocalRun/ 332 | 333 | # MSBuild Binary and Structured Log 334 | *.binlog 335 | 336 | # NVidia Nsight GPU debugger configuration file 337 | *.nvuser 338 | 339 | # MFractors (Xamarin productivity tool) working folder 340 | .mfractor/ 341 | 342 | # Local History for Visual Studio 343 | .localhistory/ 344 | 345 | # BeatPulse healthcheck temp database 346 | healthchecksdb 347 | 348 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 349 | MigrationBackup/ 350 | 351 | /SingleplayerLauncher/Properties/launchSettings.json 352 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Rechained 2 | 3 | The aim of this project is to provide the game of **Orcs Must Die! Unchained** a way to be played again. This is achieved by applying modifications to the game. 4 | 5 | **This mod will modify your game files**. But it will also create backups for the original files automatically. 6 | 7 | 8 | If you want to give feedback, contribute, participate in testing or just hang around and talk about OMDU, **feel free to join our Discord server** clicking the label below: 9 | 10 | [![OMD Modding Discord Server](https://img.shields.io/discord/583432386960818227?color=%237289da&logo=discord&logoColor=white&label=Join%20the%20Discord%20Server)](https://discord.gg/xkZskPXtwm) 11 | 12 | ### How to install 13 | 14 | #### How to install for Survival: 15 | 16 | 1. Download and Install .NET 8.0 from: [Microsoft .NET runtime](https://dotnet.microsoft.com/en-us/download/dotnet/8.0/runtime) [Microsoft .NET SDK](https://dotnet.microsoft.com/en-us/download) 17 | 2. Download Game from: [Steam](https://intradeus.github.io/http-protocol-redirector?r=steam://rungameid/427270) (If link doesn't work, ask for help at the Discord server) 18 | 3. Download the Project Rechained Launcher latest release from: [Latest release .zip](https://github.com/TimeMaster18/Project-Rechained/releases/latest/download/Project_Rechained_Launcher.zip) 19 | 4. Extract the downloaded Project_Rechained_Launcher.zip contents inside the root folder of your game installation. (Same folder that contains "Binaries", "Engine", etc) 20 | 5. Run the "ProjectRechainedLauncher.exe" inside the ProjectRechained folder. 21 | 6. (Optional) Close the Launcher and launch from Steam 22 | 23 | Note: Once installed, it can be launched from Steam by Pressing play on the Orcs Must Die! Unchained Game in your library. 24 | 25 | #### How to install for Siege (Not required for survival, requires a second game installation): 26 | [Siege installation instructions](Siege_installation_instructions.md) 27 | 28 | ### Features: 29 | - Launch the game from **Steam** 30 | - **Peer to peer multiplayer** 31 | - Survival **up to 5 players**, **Survival** or **Endless** modes 32 | - Siege 5 vs 5 players 33 | - Fully customisable loadouts, including Trap Parts 34 | - **Save your loadouts and share them with other players** 35 | - In Survival, Hero and **Trap Tiers** are matched with enemy/map level 36 | - **New Survival extra difficulties** for every map 37 | - **New Survival Mods**: 38 | - Remove Trap Cap 39 | - Invincible barricades 40 | - Custom intial Coin 41 | - God Mode 42 | - Show Trap Damage 43 | - Sell Traps during waves 44 | - and more! 45 | - Settings and mods are saved between executions 46 | 47 | ### Coming Next 48 | - Campaign Mode 49 | - Loadout randomizer 50 | - Fix (some) Coop issues 51 | 52 | ### Known Problems 53 | #### Survival 54 | - Host player has no enemy / guardian / hero voices 55 | - (Coop) Client sometimes needs to rejoin to get all skills 56 | - (Coop) Client traits "stack up" when reconnecting 57 | - Host player hero mobility skills (Gabriella teleport, Mdnight dash, etc) partially work 58 | - (Coop) Some mods create issues for Client 59 | - (Visual only) Host player wave count at top of screen is wrong (press Tab to see actual wave limit) 60 | - (Visual only) Host player Unchained meter (right side of screen) stuck after first activation 61 | - The **indicator over the hero icon at the bottom left of the screen works correctly** 62 | - (Visual only) Crogon Keep textures can be black 63 | - (Visual only) Host player own Midnight "invisibility" not showing properly 64 | 65 | #### Siege 66 | - Host player doesn't have all sounds (mostly hero and minion related) 67 | - Host Hero Upgrades popup doesn't close automatically (requires pressing U) after picking upgrade 68 | - (Visual only) Host player own Midnight "invisibility" not showing properly 69 | 70 | ## Acknowledgements 71 | 72 | This project would not have been possible without the help and support of several people and projects: 73 | 74 | A big thank you to all the people who contributed to this project: 75 | 76 | - **DaTeddy** - Contributed with reverse-engineering, testing, documenting and much more. 77 | - **DanMander** - Contributed with a more visual and extended browser-based UI. 78 | 79 | This project makes use of the following open-source projects: 80 | 81 | - **[Unreal Package Decompressor by gildor](https://github.com/gildor2/UEViewer)** - Used to decompress upk files. 82 | - **[Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json)** - Used to handle the settings and save state. 83 | - **[OMDU Hook by trumank](https://github.com/trumank/omdu-hook)** - Unlocks the possibility of multiplayer and further loadout customisation. 84 | 85 | ## Contact 86 | 87 | If you have any questions, suggestions, or feedback, feel free to reach out: 88 | 89 | - **Project Maintainer:** [TimeMaster](https://github.com/TimeMaster18) - Discord: timemaster 90 | - **Discord Server:** [OMD Modding Server](https://discord.gg/xkZskPXtwm) 91 | -------------------------------------------------------------------------------- /Siege_installation_instructions.md: -------------------------------------------------------------------------------- 1 | ## Step-by-Step Guide to Downloading OMDU Siege version Using DepotDownloader 2 | 3 | ### Prerequisites 4 | 5 | 1. **Steam Account**: Ensure you have a valid Steam account. 6 | 2. **DepotDownloader**: Download the latest version of DepotDownloader from its [GitHub repository](https://github.com/SteamRE/DepotDownloader). 7 | - [Direct download latest version of DepotDownloader](https://github.com/SteamRE/DepotDownloader/releases/latest/download/DepotDownloader-framework.zip) 8 | 9 | ### Steps to Install the Specific OMDU Siege version 10 | 11 | 1. **Download and Install .NET SDK**: 12 | - Go to the [.NET download page](https://dotnet.microsoft.com/download/dotnet). 13 | - Download and install the latest .NET SDK suitable for your operating system. 14 | 15 | 2. **Download DepotDownloader**: 16 | - If not done already, visit the [DepotDownloader releases page](https://github.com/SteamRE/DepotDownloader/releases). 17 | - Download the latest release zip file for your operating system. For Windows, it should look similar to `DepotDownloader-framework.zip` 18 | - Extract the contents of the zip file to a convenient location on your computer. 19 | - Make sure to not be within a sync'd folder such as OneDrive or MEGA. 20 | 21 | 3. **Open Command Prompt or Powershell**: 22 | - On Windows: Press `Win + R`, type `cmd`, and hit Enter. 23 | - Or Hold `Shift + Right click` in an empty space of a folder, then `Open powershell window here`. 24 | 25 | 4. **Navigate to DepotDownloader Folder**: 26 | - Use the `cd` command to navigate to the folder where you extracted DepotDownloader. 27 | - Example: `cd path\to\DepotDownloader` 28 | 29 | 5. **Run DepotDownloader Command**: 30 | - Use the following command to download the specific version: 31 | ```sh 32 | dotnet DepotDownloader.dll -app 427270 -depot 427271 -manifest 808827202674972462 -username YourSteamUsername 33 | ``` 34 | 35 | Replace `YourSteamUsername` with your actual Steam credentials. It will ask for your password once entered. 36 | 37 | **Example**: 38 | ```sh 39 | dotnet DepotDownloader.dll -app 427270 -depot 427271 -manifest 808827202674972462 -username yourusername 40 | ``` 41 | **Example with specificed directory**: 42 | ```sh 43 | dotnet DepotDownloader.dll -app 427270 -depot 427271 -manifest 808827202674972462 -username yourusername -dir C:\OMDUSiegeVersion 44 | ``` 45 | 46 | 6. **Enter Steam Guard Code (if needed)**: 47 | - If Steam Guard is enabled on your account, you may be prompted to enter a code sent to your email or generated by the Steam mobile app. 48 | 49 | 7. **Wait for the Download to Complete**: 50 | - DepotDownloader will now connect to Steam and download the specified version. 51 | - The downloaded files will be saved in a folder named `depots` within the DepotDownloader directory. 52 | 53 | 8. **Verify the Download**: 54 | - Once the download is complete, navigate to the download folder to ensure all files are there. Game root folder will be some folders deeper. 55 | 56 | 9. Now that you have OMDU Siege version downloaded, download the Project Rechained Launcher latest release from: [Latest release .zip](https://github.com/TimeMaster18/Project-Rechained/releases/latest/download/Project_Rechained_Launcher.zip) 57 | 58 | 10. Extract the downloaded Project_Rechained_Launcher.zip contents inside the root folder of your Siege game installation. (Same folder that contains "Binaries", "Engine", etc) 59 | 60 | 11. Run the "ProjectRechainedLauncher.exe" inside the ProjectRechained folder. 61 | - Siege game installation is independent of Survival (for the time being) and can't be launcher from Steam. 62 | 63 | ### Additional Tips 64 | 65 | - **Two-Factor Authentication**: If you have Steam Guard enabled, be ready to enter the code sent to your email or generated by the Steam mobile app. 66 | - **Privacy**: In case of security concerns, feel free to change your Steam password after completing the download. 67 | - If you want to have both versions (Survival and Siege), don't delete or replace any folders/files and keep both installations. 68 | - [Video Tutorial on how to download using DepotDownloader](https://www.youtube.com/watch?v=nTnmCj5v024) 69 | 70 | By following these steps, you should be able to successfully download and install the old version of OMDU using DepotDownloader with the provided App ID, Depot ID, and Manifest ID. If you encounter any issues, refer to the Discord server to seek assistance from the community on the text channels about Project Rechained. 71 | 72 | [![OMD Modding Discord Server](https://img.shields.io/discord/583432386960818227?color=%237289da&logo=discord&logoColor=white&label=Join%20the%20Discord%20Server)](https://discord.gg/xkZskPXtwm) 73 | -------------------------------------------------------------------------------- /SingleplayerLauncher/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CA1416: Validate platform compatibility 4 | dotnet_diagnostic.CA1416.severity = silent 5 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Api/JoinGameRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ProjectRechained.Api 2 | { 3 | public class JoinGameRequestDto 4 | { 5 | /// 6 | /// Language setting for the game, default is "English". 7 | /// 8 | public string GameLanguage { get; set; } = "English"; 9 | 10 | /// 11 | /// Encoded loadout. 12 | /// Represents the selected weapons, traps, guardians, etc for the player. 13 | /// 14 | public string Loadout { get; set; } 15 | 16 | /// 17 | /// Determines whether trap damage numbers should be displayed on screen. 18 | /// Defaults to `false`. 19 | /// 20 | public bool ShowTrapDamage { get; set; } = false; 21 | 22 | /// 23 | /// The IP address of the host in a multiplayer session. 24 | /// Used to connect clients to the host. Example format: "127.0.0.1" 25 | /// 26 | public string HostIP { get; set; } 27 | 28 | 29 | // Add function to pretty print the object 30 | public override string ToString() 31 | { 32 | return $"GameLanguage: {GameLanguage}, Loadout: {Loadout}, ShowTrapDamage: {ShowTrapDamage}, HostIP: {HostIP}"; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Api/LaunchGameRequestDto.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Api.Controllers; 2 | using System.Collections.Generic; 3 | 4 | namespace ProjectRechained.Api 5 | { 6 | 7 | public class LaunchGameRequestDto 8 | { 9 | /// 10 | /// Language setting for the game, default is "English". 11 | /// 12 | public string GameLanguage { get; set; } = "English"; 13 | 14 | /// 15 | /// Host or local player's name. 16 | /// 17 | public string PlayerName { get; set; } 18 | 19 | /// 20 | /// "Survival", "Endless", etc. 21 | /// 22 | public string GameMode { get; set; } 23 | 24 | /// 25 | /// For Survival, this would be "Apprentice", "War Mage", "Master", or "Rift lord". 26 | /// For Endless, can be ignored. 27 | /// 28 | public string Difficulty { get; set; } 29 | 30 | /// 31 | /// Additional difficulty settings, if applicable. 32 | /// Examples: "Apprentice+", "Master++", "Rift Lord+3" 33 | /// 34 | public string ExtraDifficulty { get; set; } 35 | 36 | /// 37 | /// Name of the map, e.g. "Cliffside Clash", "The Baths", etc. 38 | /// 39 | public string MapName { get; set; } 40 | 41 | /// 42 | /// Encoded loadouts for each player, or at least the host/local player. 43 | /// Represents selected weapons, traps, guardians, etc. 44 | /// 45 | public List Loadouts { get; set; } 46 | 47 | /// 48 | /// Names of mods to enable. They must match the internal 49 | /// `Mods.ModList` items by name. 50 | /// e.g. [ "No Trap Cap", "God Mode", "Show Trap Damage Flyoffs" ] 51 | /// 52 | public List ActiveMods { get; set; } 53 | 54 | /// 55 | /// Whether to display trap damage numbers on screen. 56 | /// 57 | public bool ShowTrapDamage { get; set; } = false; 58 | 59 | /// 60 | /// The amount of starting in-game currency for the player. 61 | /// Defaults to `GameController.DEFAULT_INVALID_VALUE` if not set. 62 | /// 63 | public int StartingCoin { get; set; } = GameController.DEFAULT_INVALID_VALUE; 64 | 65 | /// 66 | /// Overrides the default trap tier level if specified. 67 | /// Defaults to `GameController.DEFAULT_INVALID_VALUE` if not set. 68 | /// 69 | public int OverrideTrapTier { get; set; } = GameController.DEFAULT_INVALID_VALUE; 70 | 71 | /// 72 | /// Overrides the player's account level if specified. 73 | /// Defaults to `GameController.DEFAULT_INVALID_VALUE` if not set. 74 | /// 75 | public int OverrideAccountLevel { get; set; } = GameController.DEFAULT_INVALID_VALUE; 76 | 77 | 78 | // Add function to pretty print the object 79 | public override string ToString() 80 | { 81 | return $"GameLanguage: {GameLanguage}, PlayerName: {PlayerName}, GameMode: {GameMode}, Difficulty: {Difficulty}, ExtraDifficulty: {ExtraDifficulty}, MapName: {MapName}, Loadouts: {string.Join(", ", Loadouts)}, ActiveMods: {string.Join(", ", ActiveMods)}, ShowTrapDamage: {ShowTrapDamage}, StartingCoin: {StartingCoin}, OverrideTrapTier: {OverrideTrapTier}, OverrideAccountLevel: {OverrideAccountLevel}"; 82 | } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/BaseLoadouts.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SingleplayerLauncher.Configuration; 3 | using SingleplayerLauncher.GameFiles; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | namespace SingleplayerLauncher 8 | { 9 | public abstract class BaseLoadouts : IConfiguration 10 | { 11 | public abstract string FileName { get; } 12 | 13 | public List LoadoutList { get; set; } = []; 14 | 15 | protected BaseLoadouts() { } 16 | 17 | public void AddLoadout(string name, string code) 18 | { 19 | if (!LoadoutList.Exists(loadout => loadout.Name == name)) 20 | { 21 | LoadoutList.Add(new LoadoutDTO { Name = name, Code = code }); 22 | } 23 | } 24 | 25 | public void RemoveLoadout(string name) 26 | { 27 | LoadoutList.RemoveAll(loadout => loadout.Name == name); 28 | } 29 | 30 | public LoadoutDTO GetLoadoutByName(string name) 31 | { 32 | return LoadoutList.Find(l => l.Name == name); 33 | } 34 | 35 | public List GetLoadoutNames() 36 | { 37 | return LoadoutList.ConvertAll(l => l.Name); 38 | } 39 | 40 | public bool Exists(string name) 41 | { 42 | return LoadoutList.Exists(l => l.Name == name); 43 | } 44 | 45 | public bool UpdateLoadout(string name, string newCode) 46 | { 47 | var loadout = LoadoutList.Find(l => l.Name == name); 48 | if (loadout != null) 49 | { 50 | loadout.Code = newCode; 51 | return true; 52 | } 53 | return false; 54 | } 55 | 56 | public void Load() 57 | { 58 | string loadoutsFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, FileName); 59 | if (File.Exists(loadoutsFile)) 60 | { 61 | var root = JsonConvert.DeserializeObject(File.ReadAllText(loadoutsFile)); 62 | LoadoutList = root.Loadouts; 63 | } 64 | else 65 | { 66 | Save(); 67 | } 68 | } 69 | 70 | public void Save() 71 | { 72 | string loadoutsFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, FileName); 73 | var root = new LoadoutRootDTO { Loadouts = LoadoutList }; 74 | File.WriteAllText(loadoutsFile, JsonConvert.SerializeObject(root, Formatting.Indented)); 75 | } 76 | } 77 | 78 | public class LoadoutDTO 79 | { 80 | public string Name { get; set; } 81 | public string Code { get; set; } 82 | } 83 | 84 | public class LoadoutRootDTO 85 | { 86 | public List Loadouts { get; set; } = []; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/GameConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SingleplayerLauncher.Configuration; 3 | using SingleplayerLauncher.GameFiles; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | namespace SingleplayerLauncher 9 | { 10 | public class GameConfig : IConfiguration 11 | { 12 | public static readonly string GAME_CONFIG_FILE_NAME = "gameconfig.json"; 13 | private static GameConfig _instance; 14 | private static readonly object _lock = new(); 15 | 16 | public bool ModsEnabled { get; set; } 17 | public string GameMode { get; set; } = "Survival"; 18 | public string Difficulty { get; set; } 19 | public string Battleground { get; set; } 20 | public string SiegeBattleground { get; set; } 21 | public bool SiegeEnemyTeamAsBots { get; set; } 22 | public bool SiegeAllyBots { get; set; } 23 | public string ExtraDifficulty { get; set; } 24 | public string SiegeBotDifficulty { get; set; } 25 | public bool GodMode { get; set; } 26 | public bool ShowTrapDamage { get; set; } 27 | public bool NoTrapCap { get; set; } 28 | public bool InvincibleBarricades { get; set; } 29 | public bool TrapsInTraps { get; set; } 30 | public bool Hardcore { get; set; } 31 | public bool NoLimitUniqueTraps { get; set; } 32 | public bool NoTrapGrid { get; set; } 33 | public bool TrapsAnySurface { get; set; } 34 | public bool EnhancedTrapRotation { get; set; } 35 | public bool SellTrapsAnytime { get; set; } 36 | public bool CustomStartCoinEnabled { get; set; } 37 | public int StartingCoin { get; set; } 38 | public bool OverrideAccountLevel { get; set; } 39 | public int AccountLevel { get; set; } = 1; 40 | public bool OverrideTrapTier { get; set; } 41 | public int TrapTier { get; set; } = 1; 42 | public string AdditionalHeroWeapon { get; set; } = ""; 43 | public bool AdditionalHeroWeaponEnabled { get; set; } 44 | 45 | private GameConfig() { } 46 | 47 | public static GameConfig Instance 48 | { 49 | get 50 | { 51 | lock (_lock) 52 | { 53 | if (_instance == null) 54 | { 55 | _instance = new GameConfig(); 56 | } 57 | } 58 | return _instance; 59 | } 60 | } 61 | 62 | public void Load() 63 | { 64 | string gameConfigFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, GAME_CONFIG_FILE_NAME); 65 | if (File.Exists(gameConfigFile)) 66 | { 67 | var settings = JsonConvert.DeserializeObject>(File.ReadAllText(gameConfigFile)); 68 | LoadFromDictionary(settings); 69 | } 70 | else 71 | { 72 | Save(); 73 | } 74 | } 75 | 76 | private void LoadFromDictionary(Dictionary settings) 77 | { 78 | foreach (var property in GetType().GetProperties()) 79 | { 80 | if (settings.ContainsKey(property.Name) && property.CanWrite && property.Name != "Instance") 81 | { 82 | property.SetValue(this, Convert.ChangeType(settings[property.Name], property.PropertyType)); 83 | } 84 | } 85 | } 86 | 87 | public void Save() 88 | { 89 | var settings = new Dictionary(); 90 | 91 | foreach (var property in GetType().GetProperties()) 92 | { 93 | if (property.CanRead && property.Name != "Instance") 94 | { 95 | settings[property.Name] = property.GetValue(this); 96 | } 97 | } 98 | 99 | string gameConfigFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, GAME_CONFIG_FILE_NAME); 100 | File.WriteAllText(gameConfigFile, JsonConvert.SerializeObject(settings, Formatting.Indented)); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/IConfiguration.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SingleplayerLauncher.Configuration 3 | { 4 | public interface IConfiguration 5 | { 6 | 7 | void Load(); 8 | void Save(); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/Settings.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using SingleplayerLauncher.Configuration; 3 | using SingleplayerLauncher.GameFiles; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | namespace SingleplayerLauncher 9 | { 10 | public class Settings : IConfiguration 11 | { 12 | public static readonly string SETTINGS_FILE_NAME = "settings.json"; 13 | private static readonly string SETTINGS_LEGACY_FILE_NAME = "settings.txt"; 14 | private static Settings _instance; 15 | private static readonly object _lock = new(); 16 | 17 | public bool FirstRun { get; set; } = true; 18 | public bool ShowOldUI { get; set; } 19 | public bool Debug { get; set; } 20 | public bool RunAs32 { get; set; } 21 | public string Language { get; set; } = "English"; 22 | public string RootGamePath { get; set; } 23 | public string LauncherInstallationPath { get; set; } 24 | public bool IsSiegeInstallation { get; set; } = false; 25 | 26 | private Settings() { } 27 | 28 | public static Settings Instance 29 | { 30 | get 31 | { 32 | lock (_lock) 33 | { 34 | if (_instance == null) 35 | { 36 | _instance = new Settings(); 37 | } 38 | } 39 | return _instance; 40 | } 41 | } 42 | 43 | public void Load() 44 | { 45 | // TODO: Remove in future release 46 | string settingsFileLegacy = Path.Combine(RootGamePath, FileUtils.BINARIES_FOLDER_NAME, SETTINGS_LEGACY_FILE_NAME); 47 | if (File.Exists(settingsFileLegacy)) 48 | { 49 | try 50 | { 51 | File.Delete(settingsFileLegacy); 52 | } 53 | finally 54 | { 55 | // Do nothing - ignore legacy file 56 | } 57 | } 58 | 59 | string settingsFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, SETTINGS_FILE_NAME); 60 | if (File.Exists(settingsFile)) 61 | { 62 | var settings = JsonConvert.DeserializeObject>(File.ReadAllText(settingsFile)); 63 | LoadFromDictionary(settings); 64 | } 65 | else 66 | { 67 | Save(); 68 | } 69 | } 70 | 71 | private void LoadFromDictionary(Dictionary settings) 72 | { 73 | foreach (var property in GetType().GetProperties()) 74 | { 75 | if (settings.ContainsKey(property.Name) && property.CanWrite && property.Name != "Instance") 76 | { 77 | property.SetValue(this, Convert.ChangeType(settings[property.Name], property.PropertyType)); 78 | } 79 | } 80 | } 81 | 82 | public void Save() 83 | { 84 | var settings = new Dictionary(); 85 | 86 | foreach (var property in GetType().GetProperties()) 87 | { 88 | if (property.CanRead && property.Name != "Instance") 89 | { 90 | settings[property.Name] = property.GetValue(this); 91 | } 92 | } 93 | 94 | string settingsFile = Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.CONFIG_FOLDER_NAME, SETTINGS_FILE_NAME); 95 | File.WriteAllText(settingsFile, JsonConvert.SerializeObject(settings, Formatting.Indented)); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/SiegeLoadouts.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | 4 | namespace SingleplayerLauncher 5 | { 6 | public class SiegeLoadouts : BaseLoadouts 7 | { 8 | private static SiegeLoadouts _instance; 9 | private static readonly object _lock = new(); 10 | 11 | public override string FileName { get; } = "siege_loadouts.json"; 12 | 13 | private SiegeLoadouts() { } 14 | 15 | public static SiegeLoadouts Instance 16 | { 17 | get 18 | { 19 | lock (_lock) 20 | { 21 | if (_instance == null) 22 | { 23 | _instance = new SiegeLoadouts(); 24 | _instance.Load(); 25 | 26 | if (_instance.LoadoutList.Count == 0) 27 | { 28 | LoadDefaultLoadouts(); 29 | } 30 | } 31 | } 32 | return _instance; 33 | } 34 | } 35 | 36 | private static void LoadDefaultLoadouts() 37 | { 38 | _instance.LoadoutList.AddRange(new List 39 | { 40 | new() { Name = "Defender 1", Code = "Defender1-B1p3-1-3P3N1j2M1r1e2R2B2O-N1BJK9AUli-LdA5" }, 41 | new() { Name = "Defender 2", Code = "Defender2-E172-1-3P3N1j2M1w1t2Q2B1i-N1BJK9AUli-LdF5" }, 42 | new() { Name = "Pillager 1", Code = "Pillager1-20G3-3-3J3N2G292F21251i2O-61BVK9AUli-VbE7" }, 43 | new() { Name = "Attacker 1", Code = "Attacker1-90X2-2-3P3N2G292F21251i2O-NT4OdIH8qi-Wh8C" }, 44 | new() { Name = "Attacker 2", Code = "Attacker2-A0v2-2-3J3N2G292F21251i2O-Y2O4DabEpf-MjC8" } 45 | }); 46 | _instance.Save(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Configuration/SurvivalLoadouts.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | 4 | namespace SingleplayerLauncher 5 | { 6 | public class SurvivalLoadouts : BaseLoadouts 7 | { 8 | private static SurvivalLoadouts _instance; 9 | private static readonly object _lock = new(); 10 | 11 | public override string FileName { get; } = "loadouts.json"; 12 | 13 | private SurvivalLoadouts() { } 14 | 15 | public static SurvivalLoadouts Instance 16 | { 17 | get 18 | { 19 | lock (_lock) 20 | { 21 | if (_instance == null) 22 | { 23 | _instance = new SurvivalLoadouts(); 24 | _instance.Load(); 25 | 26 | if (_instance.LoadoutList.Count == 0) 27 | { 28 | LoadDefaultLoadouts(); 29 | } 30 | } 31 | } 32 | return _instance; 33 | } 34 | } 35 | 36 | private static void LoadDefaultLoadouts() 37 | { 38 | _instance.LoadoutList.AddRange(new List 39 | { 40 | new() { Name = "TimeMaster Recommendation", Code = "TimeMaster-B1s2-3G3R1j2M1v2R1x1w2B-5H-1E-UjAI-0000006DKIDJ2ScMSbISbRSW8Db" } 41 | }); 42 | _instance.Save(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Battlegrounds/Endless.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static SingleplayerLauncher.Model.Map; 4 | 5 | namespace SingleplayerLauncher.Model 6 | { 7 | class Endless : IBattleground 8 | { 9 | public int Id { get; private set; } 10 | public string Name { get; private set; } 11 | 12 | public Map Map { get; set; } 13 | 14 | public GameMode GameMode { get; set; } = GameMode.Endless; 15 | 16 | public Difficulty Difficulty { get; set; } 17 | 18 | public int StartingCoin { get; set; } 19 | 20 | public TimeSpan ParTime { get; set; } 21 | 22 | public static Endless TheBathsEndless = new() 23 | { 24 | Id = 1, 25 | Name = "The Baths", 26 | Map = TheBaths, 27 | Difficulty = Difficulty.Endless, 28 | }; 29 | 30 | public static Endless BanquetHallEndless = new() 31 | { 32 | Id = 2, 33 | Name = "Banquet Hall", 34 | Map = BanquetHall, 35 | Difficulty = Difficulty.Endless, 36 | }; 37 | 38 | public static Endless ThroneRoomEndless = new() 39 | { 40 | Id = 3, 41 | Name = "Throne Room", 42 | Map = ThroneRoom, 43 | Difficulty = Difficulty.Endless, 44 | }; 45 | 46 | public static Endless HighlandsEndless = new() 47 | { 48 | Id = 4, 49 | Name = "Highlands", 50 | Map = Highlands, 51 | Difficulty = Difficulty.Endless, 52 | }; 53 | 54 | public static Endless TheWallEndless = new() 55 | { 56 | Id = 5, 57 | Name = "The Wall", 58 | Map = TheWall, 59 | Difficulty = Difficulty.Endless, 60 | }; 61 | 62 | public static Endless ThuricvodVillageEndless = new() 63 | { 64 | Id = 6, 65 | Name = "Thuricvod Village", 66 | Map = ThuricvodVillage, 67 | Difficulty = Difficulty.Endless, 68 | }; 69 | 70 | public static Endless CrogonKeepEndless = new() 71 | { 72 | Id = 7, 73 | Name = "Crogon Keep", 74 | Map = CrogonKeep, 75 | Difficulty = Difficulty.Endless, 76 | }; 77 | 78 | public static Endless SharkIslandEndless = new() 79 | { 80 | Id = 8, 81 | Name = "Shark Island", 82 | Map = SharkIsland, 83 | Difficulty = Difficulty.Endless, 84 | }; 85 | 86 | public static Endless FrostbiteEndless = new() 87 | { 88 | Id = 9, 89 | Name = "Frostbite", 90 | Map = Frostbite, 91 | Difficulty = Difficulty.Endless, 92 | }; 93 | 94 | public static Endless AvalancheEndless = new() 95 | { 96 | Id = 10, 97 | Name = "Avalanche", 98 | Map = Avalanche, 99 | Difficulty = Difficulty.Endless, 100 | }; 101 | 102 | public static Endless CastleGatesEndless = new() 103 | { 104 | Id = 11, 105 | Name = "Castle Gates", 106 | Map = CastleGates, 107 | Difficulty = Difficulty.Endless, 108 | }; 109 | 110 | public static Endless EventideFortressEndless = new() 111 | { 112 | Id = 12, 113 | Name = "Eventide Fortress", 114 | Map = EventideFortress, 115 | Difficulty = Difficulty.Endless, 116 | }; 117 | 118 | public static Dictionary EndlessBattlegrounds = new() 119 | { 120 | { TheBaths.Name, TheBathsEndless }, 121 | { BanquetHall.Name, BanquetHallEndless }, 122 | { ThroneRoom.Name, ThroneRoomEndless }, 123 | { Highlands.Name, HighlandsEndless }, 124 | { TheWall.Name, TheWallEndless }, 125 | { ThuricvodVillage.Name, ThuricvodVillageEndless }, 126 | { CrogonKeep.Name, CrogonKeepEndless }, 127 | { SharkIsland.Name, SharkIslandEndless }, 128 | { Frostbite.Name, FrostbiteEndless }, 129 | { Avalanche.Name, AvalancheEndless }, 130 | { CastleGates.Name, CastleGatesEndless }, 131 | { EventideFortress.Name, EventideFortressEndless }, 132 | }; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Battlegrounds/IBattleground.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SingleplayerLauncher.Model 4 | { 5 | public interface IBattleground 6 | { 7 | string Name { get; } 8 | Map Map { get; set; } 9 | GameMode GameMode { get; set; } 10 | Difficulty Difficulty { get; set; } 11 | int StartingCoin { get; set; } 12 | TimeSpan ParTime { get; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Battlegrounds/Prologue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SingleplayerLauncher.Model 4 | { 5 | class Prologue : IBattleground 6 | { 7 | /* 8 | { "Prologue 1 (Grand Foyer)", "NPE_1.umap" }, 9 | { "Prologue 2 (Archmage Library)", "NPE_2.umap" }, 10 | { "Prologue 3 (Dungeon)", "NPE_3.umap" }, 11 | { "Prologue 4 (Canals)", "NPE_4.umap" }, 12 | { "Prologue 5 (Riftmaker's Temple)", "NPE_5.umap" }, 13 | //{ "SpitfireFrontEndMap", "SpitfireFrontEndMap.umap" }, 14 | { "Survival Tutorial", "TutorialSurvival.umap" }, 15 | { "Basics Tutorial", "NewbieTutorial.umap" } 16 | */ 17 | public string Name { get; private set; } 18 | 19 | public Map Map { get; set; } 20 | 21 | public GameMode GameMode { get; set; } 22 | 23 | public Difficulty Difficulty { get; set; } 24 | 25 | public int StartingCoin { get; set; } 26 | 27 | public TimeSpan ParTime => throw new NotImplementedException(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Battlegrounds/Siege.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using static SingleplayerLauncher.Model.Map; 4 | 5 | namespace SingleplayerLauncher.Model 6 | { 7 | class Siege : IBattleground 8 | { 9 | public string Name { get; private set; } 10 | 11 | public Map Map { get; set; } 12 | public GameMode GameMode { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 13 | public Difficulty Difficulty { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 14 | public int StartingCoin { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } 15 | 16 | public TimeSpan ParTime => throw new NotImplementedException(); 17 | 18 | public static Siege CliffsideClashSiege = new() 19 | { 20 | Name = "Cliffside Clash", 21 | Map = SiegeCliffsideClash 22 | }; 23 | 24 | public static Siege UnchainedFortressSiege = new() 25 | { 26 | Name = "Unchained Fortress", 27 | Map = SiegeUnchainedFortress 28 | }; 29 | 30 | public static Dictionary SiegeBattlegrounds = new() 31 | { 32 | { CliffsideClash.Name, CliffsideClashSiege }, 33 | { UnchainedFortress.Name, UnchainedFortressSiege }, 34 | }; 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/BotDifficulty.cs: -------------------------------------------------------------------------------- 1 | using static ProjectRechained.Entities.Names.BotDifficulty; 2 | using SingleplayerLauncher.Model; 3 | using System.Collections.Generic; 4 | 5 | namespace ProjectRechained.Entities 6 | { 7 | internal class BotDifficulty 8 | { 9 | public string Name { get; private set; } 10 | public int BotLevel { get; private set; } 11 | public SiegeLoadout[] botLoadouts { get; private set; } 12 | public int TrapTier { get; private set; } 13 | 14 | private BotDifficulty(string name, int botLevel, int trapTier, SiegeLoadout[] loadouts) 15 | { 16 | Name = name; 17 | BotLevel = botLevel; 18 | TrapTier = trapTier; 19 | botLoadouts = loadouts; 20 | } 21 | 22 | public static readonly BotDifficulty Easy = new( 23 | EASY, 24 | 1, 25 | 1, 26 | [ 27 | BotLoadout.BaseMaximilian, 28 | BotLoadout.BaseGabriella, 29 | BotLoadout.BaseOziel, 30 | BotLoadout.BaseIvy, 31 | BotLoadout.BaseHogarth 32 | ] 33 | ); 34 | 35 | public static readonly BotDifficulty Normal = new( 36 | NORMAL, 37 | 2, 38 | 1, 39 | [ 40 | BotLoadout.BaseMaximilian, 41 | BotLoadout.BaseGabriella, 42 | BotLoadout.BaseOziel, 43 | BotLoadout.BaseIvy, 44 | BotLoadout.BaseHogarth 45 | ] 46 | ); 47 | 48 | public static readonly BotDifficulty Hard = new( 49 | HARD, 50 | 3, 51 | 1, 52 | [ 53 | BotLoadout.BaseMaximilian, 54 | BotLoadout.BaseGabriella, 55 | BotLoadout.BaseOziel, 56 | BotLoadout.BaseIvy, 57 | BotLoadout.BaseHogarth 58 | ] 59 | ); 60 | 61 | public static readonly BotDifficulty Nightmare = new( 62 | NIGHTMARE, 63 | 4, 64 | 1, 65 | [ 66 | BotLoadout.BaseMaximilian, 67 | BotLoadout.BaseGabriella, 68 | BotLoadout.BaseOziel, 69 | BotLoadout.BaseIvy, 70 | BotLoadout.BaseHogarth 71 | ] 72 | ); 73 | 74 | public static readonly Dictionary BotDifficultiesByName = new() 75 | { 76 | { EASY, Easy }, 77 | { NORMAL, Normal }, 78 | { HARD, Hard }, 79 | { NIGHTMARE, Nightmare } 80 | }; 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/BotLoadout.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Model; 2 | using static SingleplayerLauncher.Model.Wave; 3 | using static SingleplayerLauncher.Model.Trap; 4 | using static SingleplayerLauncher.Model.Gear; 5 | using static SingleplayerLauncher.Model.SiegeTrait; 6 | 7 | namespace ProjectRechained.Entities 8 | { 9 | public class BotLoadout 10 | { 11 | // Base Waves, SlotItems, and Traits 12 | private static readonly Wave[] BaseWaves1 = 13 | { 14 | LightSoldiersLvl1Wave, GnollHuntersLvl1Wave, 15 | LightSoldiersLvl2Wave, GnollHuntersLvl2Wave, 16 | GiantsLvl3Wave, PrideHuntersLvl3Wave, 17 | GiantsLvl4Wave, FireMagesLvl4Wave, 18 | BulgodArmoredOgre, BulgodArmoredOgre 19 | }; 20 | 21 | private static readonly Wave[] BaseWaves2 = 22 | { 23 | GrizzlyBearsLvl1Wave, CrossbowOrcsLvl1Wave, 24 | GrizzlyBearsLvl2Wave, CrossbowOrcsLvl2Wave, 25 | OgresLvl3Wave, HumanCrossbowmenLvl3Wave, 26 | TrollsLvl4Wave, FireMagesLvl4Wave, 27 | BulgodArmoredOgre, BulgodArmoredOgre 28 | }; 29 | 30 | private static readonly SlotItem[] BaseDefenderSlotItems = 31 | { 32 | MendingRoot, MagesClover, 33 | Barricade, ViscousTar, Brimstone, BGHCeilingBallista, CeilingBallista, ArrowWall, null 34 | }; 35 | 36 | private static readonly SlotItem[] BaseAttackerSlotItems = 37 | { 38 | MendingRoot, MagesClover, MagesPicnic, FreedomTrinket, 39 | null, null, null, null, null 40 | }; 41 | 42 | private static readonly SiegeTrait[] BaseDefenderTraits = 43 | { 44 | DoesntPlayWellWithOthers, StrangeSenseOfHumor, WalksItOff, OneWithTheTraps 45 | }; 46 | 47 | private static readonly SiegeTrait[] BaseAttackerTraits = 48 | { 49 | BornLeader, NotScaredOfStatues, PracticesWitchcraft, MotivatesOthers 50 | }; 51 | 52 | // Static Bot Loadouts 53 | public static SiegeLoadout BaseMaximilian = new() 54 | { 55 | PlayerName = "Maximilian", 56 | Hero = Hero.Maximilian, 57 | Skin = Skin.CardboardSamurai, 58 | Role = SiegeRole.Defender, 59 | Waves = BaseWaves1, 60 | SlotItems = BaseDefenderSlotItems, 61 | Traits = BaseDefenderTraits 62 | }; 63 | 64 | public static SiegeLoadout BaseGabriella = new() 65 | { 66 | PlayerName = "Gabriella", 67 | Hero = Hero.Gabriella, 68 | Skin = Skin.MistressOfIllusion, 69 | Role = SiegeRole.Defender, 70 | Waves = BaseWaves2, 71 | SlotItems = BaseDefenderSlotItems, 72 | Traits = BaseDefenderTraits 73 | }; 74 | 75 | public static SiegeLoadout BaseSmolder = new() 76 | { 77 | PlayerName = "Smolder", 78 | Hero = Hero.Smolder, 79 | Skin = Skin.KillAuea, 80 | Role = SiegeRole.Defender, 81 | Waves = BaseWaves2, 82 | SlotItems = BaseDefenderSlotItems, 83 | Traits = BaseDefenderTraits 84 | }; 85 | 86 | public static SiegeLoadout BaseZoey = new() 87 | { 88 | PlayerName = "Zoey", 89 | Hero = Hero.Zoey, 90 | Skin = Skin.JurassicJourney, 91 | Role = SiegeRole.Defender, 92 | Waves = BaseWaves1, 93 | SlotItems = BaseDefenderSlotItems, 94 | Traits = BaseDefenderTraits 95 | }; 96 | 97 | public static SiegeLoadout BaseHogarth = new() 98 | { 99 | PlayerName = "Hogarth", 100 | Hero = Hero.Hogarth, 101 | Skin = Skin.GodOfPlunder, 102 | Role = SiegeRole.Attacker, 103 | Waves = BaseWaves1, 104 | SlotItems = BaseAttackerSlotItems, 105 | Traits = BaseAttackerTraits 106 | }; 107 | 108 | public static SiegeLoadout BaseIvy = new() 109 | { 110 | PlayerName = "Ivy", 111 | Hero = Hero.Ivy, 112 | Skin = Skin.FlowerFriend, 113 | Role = SiegeRole.Attacker, 114 | Waves = BaseWaves2, 115 | SlotItems = BaseAttackerSlotItems, 116 | Traits = BaseAttackerTraits 117 | }; 118 | 119 | public static SiegeLoadout BaseOziel = new() 120 | { 121 | PlayerName = "Oziel", 122 | Hero = Hero.Oziel, 123 | Skin = Skin.StringTheory, 124 | Role = SiegeRole.Attacker, 125 | Waves = BaseWaves1, 126 | SlotItems = BaseAttackerSlotItems, 127 | Traits = BaseAttackerTraits 128 | }; 129 | 130 | public static SiegeLoadout BaseSirWinston = new() 131 | { 132 | PlayerName = "SirWinston", 133 | Hero = Hero.SirWinston, 134 | Skin = Skin.DarkPaladin, 135 | Role = SiegeRole.Attacker, 136 | Waves = BaseWaves2, 137 | SlotItems = BaseAttackerSlotItems, 138 | Traits = BaseAttackerTraits 139 | }; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Dye.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using static SingleplayerLauncher.Names.Dye; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public class Dye : LoadoutItem 7 | { 8 | public int CodeName { get; private set; } 9 | 10 | // private constructor 11 | Dye() { } 12 | 13 | // static members 14 | public static Dye Normal = new() 15 | { 16 | Id = 1, 17 | Name = NORMAL, 18 | CodeName = 0 19 | }; 20 | public static Dye Heroic = new() 21 | { 22 | Id = 2, 23 | Name = HEROIC, 24 | CodeName = 1 25 | }; 26 | public static Dye Legendary = new() 27 | { 28 | Id = 3, 29 | Name = LEGENDARY, 30 | CodeName = 2 31 | }; 32 | 33 | public static Dictionary Dyes = new() 34 | { 35 | { NORMAL, Normal }, 36 | { HEROIC, Heroic }, 37 | { LEGENDARY, Legendary }, 38 | }; 39 | 40 | public static Dictionary DyesById = new() 41 | { 42 | { Normal.Id, Normal }, 43 | { Heroic.Id, Heroic }, 44 | { Legendary.Id, Legendary }, 45 | }; 46 | 47 | public static Dye GetById(int id) 48 | { 49 | if (DyesById.TryGetValue(id, out var dye)) 50 | { 51 | return dye; 52 | } 53 | return new Dye { Id = 0, Name = "Unknown" }; 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/GameMode.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using static SingleplayerLauncher.Names.GameMode; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public class GameMode 7 | { 8 | 9 | public string Name { get; private set; } 10 | public int Id { get; private set; } // integer used in the .ini config 11 | 12 | // private constructor 13 | GameMode() { } 14 | 15 | // static members 16 | public static GameMode Survival = new() 17 | { 18 | Name = SURVIVAL, 19 | Id = 1 20 | }; 21 | public static GameMode Endless = new() 22 | { 23 | Name = ENDLESS, 24 | Id = 5 25 | }; 26 | public static GameMode WeeklyChallenge = new() 27 | { 28 | Name = WEEKLY_CHALLENGE, 29 | Id = 1 30 | }; 31 | 32 | public static Dictionary GameModes = new() 33 | { 34 | { SURVIVAL, Survival }, 35 | { ENDLESS, Endless }, 36 | // { WEEKLY_CHALLENGE, WeeklyChallenge }, 37 | }; 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Loadout/Archetype.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Model 2 | { 3 | class Archetype : SlotItem 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Loadout/Glyph.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using static SingleplayerLauncher.Names.Glyph; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public class Glyph : SlotItem 7 | { 8 | 9 | // private constructor 10 | Glyph() { } 11 | 12 | // static members 13 | public static Glyph GlyphOfAmplification = new() 14 | { 15 | Id = 300, 16 | Name = GLYPH_OF_AMPLIFICATION, 17 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphAmplification", 18 | SiegeDescription = "Amplifies the abilities of minions with RP values that cross it. Glyphs may only be placed in dedicated glyph spaces." 19 | }; 20 | public static Glyph GlyphOfBlood = new() 21 | { 22 | Id = 301, 23 | Name = GLYPH_OF_BLOOD, 24 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphBlood", 25 | SiegeDescription = "Grants a damage boost to minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 26 | }; 27 | public static Glyph GlyphOfHealing = new() 28 | { 29 | Id = 302, 30 | Name = GLYPH_OF_HEALING, 31 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphHealing", 32 | SiegeDescription = "Grants health to minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 33 | }; 34 | public static Glyph GlyphOfHealth = new() 35 | { 36 | Id = 303, 37 | Name = GLYPH_OF_HEALTH, 38 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphHealth", 39 | SiegeDescription = "Grants minor health regeneration to minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 40 | }; 41 | public static Glyph GlyphOfMagicArmor = new() 42 | { 43 | Id = 304, 44 | Name = GLYPH_OF_MAGIC_ARMOR, 45 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphArmorMagic", 46 | SiegeDescription = "Grants magic armor to minions that cross it. Magic armor protects against elemental damage. Glyphs may only be placed in dedicated glyph spaces." 47 | }; 48 | public static Glyph GlyphOfPhysicalArmor = new() 49 | { 50 | Id = 305, 51 | Name = GLYPH_OF_PHYSICAL_ARMOR, 52 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphPhysicalArmor", 53 | SiegeDescription = "Increases the physical armor of allied minions who cross it by 20. Glyphs may only be placed in dedicated glyph spaces." 54 | }; 55 | public static Glyph GlyphOfSneaking = new() 56 | { 57 | Id = 306, 58 | Name = GLYPH_OF_SNEAKING, 59 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphStealth", 60 | SiegeDescription = "Briefly grants stealth to minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 61 | }; 62 | public static Glyph GlyphOfSpeed = new() 63 | { 64 | Id = 307, 65 | Name = GLYPH_OF_SPEED, 66 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphSpeed", 67 | SiegeDescription = "Increases the movement speed of minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 68 | }; 69 | public static Glyph MinorGlyphOfHealing = new() 70 | { 71 | Id = 308, 72 | Name = MINOR_GLYPH_OF_HEALING, 73 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphHealingMinor", 74 | SiegeDescription = "Heals minions that cross it. Glyphs may only be placed in dedicated glyph spaces." 75 | }; 76 | public static Glyph OrcishFieldPromotionGlyph = new() 77 | { 78 | Id = 309, 79 | Name = ORCISH_FIELD_PROMOTION_GLYPH, 80 | ItemTemplateName = "SpitfireGame.RItemTrapGlyphOrcishPromotion", 81 | SiegeDescription = "Promotes 5 orc minions that cross the glyph. Light orcs are promoted to medium orcs, medium to heavy. Glyph expires after 20 promotions." 82 | }; 83 | 84 | public static Dictionary Glyphs = new() 85 | { 86 | { GLYPH_OF_AMPLIFICATION, GlyphOfAmplification }, 87 | { GLYPH_OF_BLOOD, GlyphOfBlood }, 88 | { GLYPH_OF_HEALING, GlyphOfHealing }, 89 | { GLYPH_OF_HEALTH, GlyphOfHealth }, 90 | { GLYPH_OF_MAGIC_ARMOR, GlyphOfMagicArmor }, 91 | { GLYPH_OF_PHYSICAL_ARMOR, GlyphOfPhysicalArmor }, 92 | { GLYPH_OF_SNEAKING, GlyphOfSneaking }, 93 | { GLYPH_OF_SPEED, GlyphOfSpeed }, 94 | { MINOR_GLYPH_OF_HEALING, MinorGlyphOfHealing }, 95 | { ORCISH_FIELD_PROMOTION_GLYPH, OrcishFieldPromotionGlyph } 96 | }; 97 | 98 | public static Dictionary GlyphsById = new() 99 | { 100 | { GlyphOfAmplification.Id, GlyphOfAmplification }, 101 | { GlyphOfBlood.Id, GlyphOfBlood }, 102 | { GlyphOfHealing.Id, GlyphOfHealing }, 103 | { GlyphOfHealth.Id, GlyphOfHealth }, 104 | { GlyphOfMagicArmor.Id, GlyphOfMagicArmor }, 105 | { GlyphOfPhysicalArmor.Id, GlyphOfPhysicalArmor }, 106 | { GlyphOfSneaking.Id, GlyphOfSneaking }, 107 | { GlyphOfSpeed.Id, GlyphOfSpeed }, 108 | { MinorGlyphOfHealing.Id, MinorGlyphOfHealing }, 109 | { OrcishFieldPromotionGlyph.Id, OrcishFieldPromotionGlyph } 110 | }; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Loadout/SiegeRole.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using static SingleplayerLauncher.Names.SiegeRole; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public class SiegeRole : Trait 7 | { 8 | // private constructor 9 | SiegeRole() { } 10 | public string Effect { get; private set; } 11 | 12 | // static members 13 | public static SiegeRole Defender = new() 14 | { 15 | Id = 1, 16 | Name = DEFENDER, 17 | Effect = "+10% XP from minion kills. Starts match with coin.", 18 | SiegeDescription = "Defends the rift by building traps and killing minions.", 19 | CodeName = "SpitfireGame.RWeaverRoleDefense" 20 | }; 21 | public static SiegeRole Attacker = new() 22 | { 23 | Id = 2, 24 | Name = ATTACKER, 25 | Effect = "+8% leadership aura and XP when escorting minions.", 26 | SiegeDescription = "Leads minions through enemy defenses to attack enemy rifts.", 27 | CodeName = "SpitfireGame.RWeaverRoleOffense" 28 | }; 29 | public static SiegeRole Pillager = new() 30 | { 31 | Id = 3, 32 | Name = PILLAGER, 33 | Effect = "+18% XP from cache drops.", 34 | SiegeDescription = "Finds and loots caches. Assists defenders and attackers.", 35 | CodeName = "SpitfireGame.RWeaverRolePillaging" 36 | }; 37 | 38 | public static Dictionary Roles = new() 39 | { 40 | { DEFENDER, Defender }, 41 | { ATTACKER, Attacker }, 42 | { PILLAGER, Pillager } 43 | }; 44 | 45 | public static Dictionary RolesById = new() 46 | { 47 | { Defender.Id, Defender }, 48 | { Attacker.Id, Attacker }, 49 | { Pillager.Id, Pillager } 50 | }; 51 | 52 | public static SiegeRole GetById(int id) 53 | { 54 | if (RolesById.TryGetValue(id, out var trait)) 55 | { 56 | return trait; 57 | } 58 | return new SiegeRole { Id = 0, Name = "Unknown" }; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Loadout/Trait.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Model 2 | { 3 | public class Trait : LoadoutItem 4 | { 5 | public const string PENTAGON_BONUS_SLOT = "Pentagon"; 6 | public const string DIAMOND_BONUS_SLOT = "Diamond"; 7 | public const string TRIANGLE_BONUS_SLOT = "Triangle"; 8 | 9 | public string CodeName { get; set; } 10 | public string MatchingSlot { get; set; } 11 | public string UnlockedAt { get; set; } 12 | public string Rarity { get; set; } 13 | public Trait MatchingBonusTrait { get; set; } 14 | 15 | // Bonus Traits 16 | public static Trait BONUS_TRAIT_ARMOR = new() 17 | { 18 | CodeName = "SpitfireGame.RWeaverBonusTraitArmor", 19 | Description = "+8 armor.", 20 | SiegeDescription = "+8 armor." 21 | }; 22 | public static Trait BONUS_TRAIT_CC_RESIST = new() 23 | { 24 | CodeName = "SpitfireGame.RWeaverBonusTraitCCResist", 25 | Description = "-10% control effect duration.", 26 | SiegeDescription = "-10% control effect duration." 27 | }; 28 | public static Trait BONUS_TRAIT_COIN_TRICKLE = new() 29 | { 30 | CodeName = "SpitfireGame.RWeaverBonusTraitCoinTrickle", 31 | Description = "+1 coin per second.", 32 | SiegeDescription = "+1 coin per second." 33 | }; 34 | public static Trait BONUS_TRAIT_COOLDOWN_REDUCTION = new() 35 | { 36 | CodeName = "SpitfireGame.RWeaverBonusTraitCooldownReduction", 37 | Description = "+10.0% cooldown speed.", 38 | SiegeDescription = "+10.0% cooldown speed." 39 | }; 40 | public static Trait BONUS_TRAIT_HEALTH = new() 41 | { 42 | CodeName = "SpitfireGame.RWeaverBonusTraitHealth", 43 | Description = "+4.0% max health.", 44 | SiegeDescription = "+4.0% max health." 45 | }; 46 | public static Trait BONUS_TRAIT_HEALTH_REGEN = new() 47 | { 48 | CodeName = "SpitfireGame.RWeaverBonusTraitHealthRegen", 49 | Description = "Regenerates 1% max health per second.", 50 | SiegeDescription = "+12.0% health regeneration." 51 | }; 52 | public static Trait BONUS_TRAIT_MAGIC_ARMOR = new() 53 | { 54 | CodeName = "SpitfireGame.RWeaverBonusTraitMagicArmor", 55 | Description = "+8 magic armor.", 56 | SiegeDescription = "+8 magic armor." 57 | }; 58 | public static Trait BONUS_TRAIT_MANA_MAX = new() 59 | { 60 | CodeName = "SpitfireGame.RWeaverBonusTraitManaMax", 61 | Description = "+8.0% max mana.", 62 | SiegeDescription = "+8.0% max mana." 63 | }; 64 | public static Trait BONUS_TRAIT_MANA_REGEN = new() 65 | { 66 | CodeName = "SpitfireGame.RWeaverBonusTraitManaRegen", 67 | Description = "+12.0% mana regeneration.", 68 | SiegeDescription = "+12.0% mana regeneration." 69 | }; 70 | public static Trait BONUS_TRAIT_PRIMARY_DAMAGE = new() 71 | { 72 | CodeName = "SpitfireGame.RWeaverBonusTraitPrimaryDamage", 73 | Description = "+5.0% primary attack damage.", 74 | SiegeDescription = "+5.0% primary attack damage." 75 | }; 76 | public static Trait BONUS_TRAIT_SPEED = new() 77 | { 78 | CodeName = "SpitfireGame.RWeaverBonusTraitSpeed", 79 | Description = "+2.5% movement speed.", 80 | SiegeDescription = "+2.5% movement speed." 81 | }; 82 | } 83 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/BotDifficulty.cs: -------------------------------------------------------------------------------- 1 | namespace ProjectRechained.Entities.Names 2 | { 3 | public class BotDifficulty 4 | { 5 | public const string 6 | EASY = "Easy", 7 | NORMAL = "Normal", 8 | HARD = "Hard", 9 | NIGHTMARE = "Nightmare", 10 | 11 | NIGHTMARE_PLUS = "Nightmare", 12 | NIGHTMARE_PLUS_2 = "Nightmare+", 13 | 14 | HELL = "Hell", 15 | HELL_PLUS = "Hell+", 16 | HELL_2 = "Hell++", 17 | 18 | IMPOSSIBLE = "Impossible", 19 | IMPOSSIBLE_PLUS = "Impossible+", 20 | IMPOSSIBLE_2 = "Impossible++", 21 | 22 | TIMEMASTER = "TimeMaster"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Consumable.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Consumable 4 | { 5 | public const string 6 | BARGAIN_SCROLL = "Discount Scroll", 7 | COOLDOWN_POTION = "Caffeination Potion", 8 | EMPTY_RIFT_SCROLL = "Empty Rift Scroll", 9 | GUARDIAN_POTION = "Guardian Potion", 10 | HEALTH_POTION = "Health Potion", 11 | INVULNERABILITY_SCROLL = "Invulnerability Scroll", 12 | LUCK_POTION = "Luck Potion", 13 | MANA_POTION = "Mana Potion", 14 | RAGE_POTION = "Rage Potion", 15 | REPAIR_SCROLL = "Repair Scroll", 16 | RESURRECTION_SCROLL = "Resurrection Scroll", 17 | SLOW_SCROLL = "Slow Scroll", 18 | SPEED_POTION = "Speed Potion", 19 | UNCHAINED_SCROLL = "Unchained Scroll", 20 | EXPERIENCE_POTION = "Experience Potion"; 21 | } 22 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Difficulty.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public class Difficulty 4 | { 5 | public const string 6 | APPRENTICE = "Apprentice", 7 | WAR_MAGE = "War Mage", 8 | MASTER = "Master", 9 | RIFT_LORD = "Rift Lord", 10 | 11 | APPRENTICE_PLUS = "Apprentice+", 12 | WAR_MAGE_PLUS = "War Mage+", 13 | MASTER_PLUS = "Master+", 14 | MASTER_PLUS_2 = "Master++", 15 | RIFT_LORD_PLUS = "Rift Lord+", 16 | RIFT_LORD_PLUS_2 = "Rift Lord++", 17 | RIFT_LORD_PLUS_3 = "Rift Lord+3", 18 | RIFT_LORD_PLUS_4 = "Rift Lord+4", 19 | RIFT_LORD_PLUS_5 = "Rift Lord+5", 20 | 21 | ENDLESS = "Endless", 22 | 23 | ENDLESS_PLUS = "Endless+", 24 | ENDLESS_PLUS_2 = "Endless++", 25 | ENDLESS_PLUS_3 = "Endless+3", 26 | ENDLESS_PLUS_4 = "Endless+4", 27 | ENDLESS_PLUS_5 = "Endless+5", 28 | ENDLESS_PLUS_6 = "Endless+6", 29 | ENDLESS_PLUS_7 = "Endless+7", 30 | ENDLESS_PLUS_8 = "Endless+8", 31 | ENDLESS_PLUS_9 = "Endless+9", 32 | ENDLESS_PLUS_10 = "Endless+10"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Dye.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | class Dye 4 | { 5 | public const string 6 | NORMAL = "Normal", 7 | HEROIC = "Heroic", 8 | LEGENDARY = "Legendary"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/GameMode.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | class GameMode 4 | { 5 | public const string 6 | SURVIVAL = "Survival", 7 | ENDLESS = "Endless", 8 | WEEKLY_CHALLENGE = "Weekly Challenge"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Gear.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Gear 4 | { 5 | public const string 6 | FREEDOM_TRINKET = "Freedom Trinket", 7 | GREATER_FREEDOM_TRINKET = "Greater Freedom Trinket", 8 | MENDING_ROOT = "Mending Root", 9 | HOBGOBLIN_CHARM = "Hobgoblin's Charm", 10 | RING_OF_LAST_STAND = "Ring of Last Stand", 11 | MAGES_PICNIC = "Mage's Picnic", 12 | MAGES_CLOVER = "Mage's Clover", 13 | GNOMISH_REPAIR_KIT = "Gnomish Repair Kit", 14 | TELEPORTATION_RING = "Teleportation Ring", 15 | ARCANE_BUBBLE_BLOWER = "Arcane Bubble Blower", 16 | FIRE_WALL_BRACERS = "Fire Wall Bracers", 17 | ICE_AMULET = "Ice Amulet", 18 | RING_OF_STORMS = "Ring of Storms", 19 | LIGHTNING_RING = "Lightning Ring", 20 | FLAME_BRACERS = "Flame Bracers", 21 | ANTITRAP_VAMBRACE = "AntiTrap Vambrace"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Glyph.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | class Glyph 4 | { 5 | public const string 6 | GLYPH_OF_AMPLIFICATION = "Glyph of Amplification", 7 | GLYPH_OF_MAGIC_ARMOR = "Glyph of Magic Armor", 8 | GLYPH_OF_BLOOD = "Glyph of Blood", 9 | GLYPH_OF_HEALING = "Glyph of Healing", 10 | MINOR_GLYPH_OF_HEALING = "Minor Glyph of Healing", 11 | GLYPH_OF_HEALTH = "Glyph of Health", 12 | ORCISH_FIELD_PROMOTION_GLYPH = "Orcish Field Promotion Glyph", 13 | GLYPH_OF_PHYSICAL_ARMOR = "Glyph of Physical Armor", 14 | GLYPH_OF_SPEED = "Glyph of Speed", 15 | GLYPH_OF_SNEAKING = "Glyph of Sneaking"; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Guardian.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Guardian 4 | { 5 | public const string 6 | BARTENDER_GUARDIAN = "Bartender Guardian", 7 | BLACKSMITH_GUARDIAN = "Blacksmith Guardian", 8 | COOK_GUARDIAN = "Cook Guardian", 9 | DECKHAND_GUARDIAN = "Deckhand Guardian", 10 | DRAGON_GUARDIAN = "Dragon Guardian", 11 | FRIAR_GUARDIAN = "Friar Guardian", 12 | HEADHUNTER_GUARDIAN = "Headhunter Guardian", 13 | JADE_EMPIRE_GUARDIAN = "Jade Empire Guardian", 14 | JAILER_GUARDIAN = "Jailer Guardian", 15 | LION_GUARDIAN = "Lion Guardian", 16 | MOON_GUARDIAN = "Moon Guardian", 17 | PRIEST_GUARDIAN = "Priest Guardian", 18 | QUARTERMASTER_GUARDIAN = "Quartermaster Guardian", 19 | RANCH_HAND_GUARDIAN = "Ranch Hand Guardian", 20 | RUMRUDDER_GUARDIAN = "Rumrudder Guardian", 21 | SERPENT_GUARDIAN = "Serpent Guardian", 22 | STABLEHAND_GUARDIAN = "Stablehand Guardian", 23 | SUN_GUARDIAN = "Sun Guardian", 24 | WEAPONWRIGHT_GUARDIAN = "WeaponWright Guardian"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Hero.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Hero 4 | { 5 | public const string 6 | BIONKA = "Bionka", 7 | BLACKPAW = "Blackpaw", 8 | BLOODSPIKE = "Bloodspike", 9 | BRASS = "Brass", 10 | CYGNUS = "Cygnus", 11 | DEADEYE = "Deadeye", 12 | DOBBIN = "Dobbin", 13 | GABRIELLA = "Gabriella", 14 | HOGARTH = "Hogarth", 15 | IVY = "Ivy", 16 | MAXIMILIAN = "Maximilian", 17 | MIDNIGHT = "Midnight", 18 | OZIEL = "Oziel", 19 | SMOLDER = "Smolder", 20 | STINKEYE = "Stinkeye", 21 | TEMPER = "Temper", 22 | TUNDRA = "Tundra", 23 | YI_LIN = "Yi-Lin", 24 | ZOEY = "Zoey", 25 | SIR_WINSTON = "Sir Winston", 26 | KOBOLD_KING = "Kobold King"; 27 | } 28 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Map.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Map 4 | { 5 | public const string 6 | ACADEMY_SEWERS = "Academy Sewers", 7 | ARCHMAGE_LIBRARY = "Archmage Library", 8 | AVALANCHE = "Avalanche", 9 | BANQUET_HALL = "Banquet Hall", 10 | CASTLE_GATES = "Castle Gates", 11 | CLIFFSIDE_CLASH = "Cliffside Clash", 12 | CONFLUENCE = "Confluence", 13 | CROGON_KEEP = "Crogon Keep", 14 | DOCKS_AT_EVENTIDE = "Docks at Eventide", 15 | EVENTIDE_FORTRESS = "Eventide Fortress", 16 | EVENTIDE_RAMPARTS = "Eventide Ramparts", 17 | FROSTBITE = "Frostbite", 18 | GATES_OF_THURICVOD = "Gates of Thuricvod", 19 | HIGHLANDS = "Highlands", 20 | MAXIMUM_SECURITY = "Maximum Security", 21 | MIDNIGHT_MARKET = "Midnight Market", 22 | ORCATRAZ = "Orcatraz", 23 | ORCRI_LA = "Orcri-La", 24 | RESTRICTED_SECTION = "Restricted Section", 25 | RIFTMAKERS_TEMPLE = "Riftmaker's Temple", 26 | SHARK_ISLAND = "Shark Island", 27 | STABLES_AT_EVENTIDE = "Stables at Eventide", 28 | STORM_DRAIN = "Storm Drain", 29 | TEMPLE_GRAVEYARD = "Temple Graveyard", 30 | THE_BATHS = "The Baths", 31 | THE_FALLING_FOLLY = "The Falling Folly", 32 | THE_WALL = "The Wall", 33 | THRONE_ROOM = "Throne Room", 34 | THURICVOD_VILLAGE = "Thuricvod Village", 35 | TRAINING_GROUNDS = "Training Grounds", 36 | UNCHAINED_FORTRESS = "Unchained Fortress", 37 | WATER_GARDEN = "Water Garden", 38 | SIEGE_TUTORIAL = "Siege Tutorial"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Mod.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { public static class Mod 3 | { 4 | public const string 5 | NO_TRAP_CAP = "No Trap Cap", 6 | NO_LIMIT_UNIQUE_TRAPS = "No Limit Unique Traps", 7 | INVINCIBLE_BARRICADES = "Invincible Barricades", 8 | TRAPS_ANY_SURFACE = "Traps Any Surface", 9 | TRAPS_IN_TRAPS = "Traps In Traps", 10 | NO_TRAP_GRID = "No Trap Grid", 11 | GOD_MODE = "God Mode", 12 | SHOW_TRAP_DAMAGE_FLYOFFS = "Show Trap Damage Flyoffs", 13 | HARDCORE = "Hardcore", 14 | ENHANCED_TRAP_ROTATION = "Enhanced Trap Rotation", 15 | SELL_TRAPS_ANYTIME = "Sell Traps Anytime", 16 | STARTING_COIN_OVERRIDE = "Starting Coin Override", 17 | ACCOUNT_LEVEL_OVERRIDE = "Account Level Override", 18 | TRAP_TIER_OVERRIDE = "Trap Tier Override", 19 | ADDITIONAL_HERO_WEAPON = "Additional Hero Weapon"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/SiegeRole.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | class SiegeRole 4 | { 5 | public const string 6 | ATTACKER = "Attacker", 7 | DEFENDER = "Defender", 8 | PILLAGER = "Pillager"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Skin.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | class Skin 4 | { 5 | public const string 6 | // OZIEL 7 | OZIEL_DEFAULT = "Oziel Default", 8 | BLOOD_RUNE = "Blood Rune", 9 | STRING_THEORY = "String Theory", 10 | KNIGHT_WALKER = "Knight Walker", 11 | 12 | // ZOEY 13 | ZOEY_DEFAULT = "Zoey Default", 14 | SCHOOL_DAZE = "School Daze", 15 | PRETTY_LITTLE_STITCHES = "Pretty Little Stitches", 16 | JURASSIC_JOURNEY = "Jurassic Journey", 17 | 18 | // BIONKA 19 | BIONKA_DEFAULT = "Bionka Default", 20 | LIZARD_QUEEN = "Lizard Queen", 21 | FLUFFALUMP = "Fluffalump", 22 | BIONKA_BUNNY = "Bionka Bunny", 23 | 24 | // BLACKPAW 25 | BLACKPAW_ENCHANTED_ARMOR = "Blackpaw Enchanted Armor", 26 | PAW_OF_THE_DEAD = "Paw of the Dead", 27 | BLACKPAW_DEFAULT = "Blackpaw Default", 28 | TUTU_DLOO = "Tutu D'Loo", 29 | PRIDE_OF_THE_PACK = "Pride of the Pack", 30 | RUNECLAW = "Runeclaw", 31 | BLACKSCALE = "Blackscale", 32 | DRAGON_THRALL = "Dragon Thrall", 33 | 34 | // BLOODPSIKE 35 | BLOODPSIKE_DEFAULT = "Bloodpsike Default", 36 | GRIZZLY_KILL = "Grizzly Kill", 37 | BREAKER_OF_MOLDS = "Breaker of Molds", 38 | BLOOD_APPETIT = "Blood Appetit", 39 | GREEN_GUILLOTINE = "Green Guillotine", 40 | LAUGHING_TO_DEATH = "Laughing to Death", 41 | 42 | // BRASS 43 | BRASS_DEFAULT = "Brass Default", 44 | GUNS_GEARS_N_LACE = "Guns, Gears, 'n Lace", 45 | BOMBSHELL_BATTALION = "Bombshell Battalion", 46 | 47 | // HOGARTH 48 | HOGARTH_ENCHANTED_ARMOR = "Hogarth Enchanted Armor", 49 | GOD_OF_PLUNDER = "God of Plunder", 50 | HOGARTH_DEFAULT = "Hogarth Default", 51 | LUMBERING_JACK = "Lumbering Jack", 52 | HOGARTH_DEFAULT_GOLD = "Hogarth Default (with some gold)", 53 | ICE_ARMOR = "Ice Armor", 54 | IMPERIAL_GOLDEN_WARRIOR = "Imperial Golden Warrior", 55 | BEACHED_BOD = "Beached Bod", 56 | BLACK_THANE = "Black Thane", 57 | THE_SCHLING = "The Schling", 58 | DRAGON_WARD = "Dragon Ward", 59 | 60 | // DEADEYE 61 | DEADEYE_DEFAULT = "Deadeye Default", 62 | PRETTY_PUNK = "Pretty Punk", 63 | THE_GOOD_THE_BAD_AND_THE_DEADEYE = "The Good, the Bad, and the Deadeye", 64 | 65 | // MIDNIGHT 66 | MIDNIGHT_DEFAULT = "Midnight Default", 67 | HIDDEN_TIGER = "Hidden Tiger", 68 | MIDNIGHT_DEFAULT_GOLD = "Midnight Default (with some gold)", 69 | CAT_BURGLAR = "Cat Burglar", 70 | SHIVER_ME_WHISKERS = "Shiver Me Whiskers", 71 | WU_XING_AZURE_ASSASSIN = "Wu Xing Azure Assassin", 72 | 73 | // DOBBIN 74 | DOBBIN_DEFAULT = "Dobbin Default", 75 | GOLD_RUSH = "Gold Rush", 76 | DOBBIN_DEFAULT_GOLD = "Dobbin Default (with some gold)", 77 | EL_CAVADOR = "El Cavador", 78 | SANTAS_LITTLE_HELPER = "Santa's Little Helper", 79 | 80 | // IVY 81 | IVY_ENCHANTED_ARMOR = "Ivy Enchanted Armor", 82 | VALKYRIE = "Valkyrie", 83 | IVY_DEFAULT = "Ivy Default", 84 | FLOWER_FRIEND = "Flower Friend", 85 | IVY_DEFAULT_GOLD = "Ivy Default (with some gold)", 86 | IMPERIAL_RUBY_ARCHER = "Imperial Ruby Archer", 87 | WICKED_WARDEN = "Wicked Warden", 88 | GROVEWATCH = "Grovewatch", 89 | DRAGON_TAMER = "Dragon Tamer", 90 | 91 | //SMOLDER 92 | SMOLDER_DEFAULT = "Smolder Default", 93 | HELTER_SWELTER = "Helter Swelter", 94 | SMOLDER_DEFAULT_WHITE_HAIR = "Smolder Default (White hair)", 95 | ELITE = "Elite", 96 | KILL_AUEA = "Kill-auea", 97 | FIRE_ALARM_FEMME = "Fire-Alarm Femme", 98 | FIRESTARTER = "Firestarter", 99 | WU_XING_DRAGON_MAGE = "Wu Xing Dragon Mage", 100 | 101 | //GABRIELLA 102 | VIOLENT_VINTAGE = "Violent Vintage", 103 | GABRIELLA_ENCHANTED_ARMOR = "Gabriella Enchanted Armor", 104 | BLOOD_QUEEN = "Blood Queen", 105 | GABRIELLA_DEFAULT = "Gabriella Default", 106 | FRIGHTFULLY_DELIGHTFUL = "Frightfully Delightful", 107 | GABRIELLA_DEFAULT_RED = "Gabriella Default (Red)", 108 | LIFE_IN_PLASTIC = "Life In Plastic", 109 | ARCHMAGE_OF_THE_ORDER = "Archmage of the Order", 110 | MISTRESS_OF_ILLUSION = "Mistress of Illusion", 111 | PROMISING_PRODIGY = "Promising Prodigy", 112 | I_DREAM_OF_GABBY = "I Dream of Gabby", 113 | MIRACLE_WORKER = "Miracle Worker", 114 | WINTER_WITCH = "Winter Witch", 115 | SKULL_NINJA = "Skull Ninja", 116 | EVIL_WAYS = "Evil Ways", 117 | DRAGON_CHARMER = "Dragon Charmer", 118 | 119 | //STINKEYE 120 | FIRST_EYE_BLIND = "First Eye Blind", 121 | STINKEYE_DEFAULT = "Stinkeye Default", 122 | STINKEYE_DEFAULT_ALT = "Stinkeye Default (Alternate)", 123 | ENFANT_TERRIBLE = "Enfant Terrible", 124 | FLOATY_FUN = "Floaty Fun", 125 | 126 | // TEMPER 127 | TEMPER_DEFAULT = "Temper Default", 128 | FORGED_FURY = "Forged Fury", 129 | PRIZED_COW = "Prized Cow", 130 | 131 | // CYGNUS 132 | CYGNUS_DEFAULT = "Cygnus Default", 133 | ADVENTURER = "Adventurer", 134 | CYGNUS_DEFAULT_RED = "Cygnus Default (Red)", 135 | CYGNUS_UNKNOWN = "Cygnus Unknown", 136 | OLD_CODGER = "Old Codger", 137 | ROAD_MASTER = "Road Master", 138 | 139 | // TUNDRA 140 | TUNDRA_DEFAULT = "Tundra Default", 141 | WAR_CHIEF = "War Chief", 142 | BIG_LEBEARSKI = "Big Lebearski", 143 | KING_OF_ARCTOS = "King of Arctos", 144 | IMPERIAL_GOLDEN_MAGE = "Imperial Golden Mage", 145 | 146 | // MAXIMILLIAN 147 | LUCKY_TUNIC = "Lucky Tunic", 148 | MAXIMILLIAN_ENCHANTED_ARMOR = "Maximillian Enchanted Armor", 149 | KNIGHTS_WATCH = "Knight's Watch", 150 | MAXIMILLIAN_DEFAULT = "Maximillian Default", 151 | ORC_SLAYER = "Orc Slayer", 152 | CARDBOARD_SAMURAI = "Cardboard Samurai", 153 | SCARED_CROW = "Scared-Crow", 154 | LION_HEART = "Lion Heart", 155 | ROBIN_HOOD = "Robin Hood", 156 | PAXIMILLIAN = "Paximillian", 157 | MAXIMILLIAN_LEGENDARY_DEFAULT = "Legendary (default)", 158 | CHAMPION_OF_THE_ORDER = "Champion of the Order", 159 | JAMEZ_RIPHER = "Jamez Ripher", 160 | WINTER_WARRIOR = "Winter Warrior", 161 | BOOMSTICK = "Boomstick", 162 | BACKDRAFT = "Backdraft", 163 | DRAGON_SLAYER = "Dragon Slayer", 164 | SUMMER_OF_STUNNING = "Summer of Stunning", 165 | RED_SCARF = "Red Scarf (China Ad)", 166 | YELLOW_SCARF = "Yellow Scarf (China Ad)", 167 | BLUE_SCARF = "Blue Scarf (China Ad)", 168 | 169 | // YI_LIN 170 | YI_LIN_DEFAULT = "Yi-lin Default", 171 | SENTINEL_OF_THE_ORDER = "Sentinel of the Order", 172 | SLAYING_IN_THE_RAIN = "Slaying in the Rain", 173 | YULE_LIN = "Yule-Lin", 174 | 175 | // SIR WINSTON 176 | DARK_PALADIN = "Dark Paladin", 177 | SIR_WINSTON_DEFAULT = "SirWinston Default", 178 | 179 | // CHANG'E 180 | CHANG_E_DEFAULT = "Chang_e Default", 181 | 182 | // CHICKEN 183 | CHICKEN = "Chicken", 184 | CHICKEN_WITH_TRAINERS = "Chicken with Trainers", 185 | 186 | // MERCENARIES 187 | PRIDE_ASSASSIN = "Pride Assassin", 188 | BOUNCER_BEAR = "Bouncer Bear", 189 | IMAGE_HUMAN = "iMage Human", 190 | IMAGE_ARCANE = "iMage Arcane", 191 | IMAGE_LIGHTNING = "iMage Lightning", 192 | IMAGE_ICE = "iMage Ice", 193 | IMAGE_FIRE = "iMage Fire", 194 | CHAOTIC_KOBOLD = "Chaotic Kobold", 195 | DWARF_PRIEST = "Dwarf Priest", 196 | SCURVY_RUMRUDDER = "Scurvy Rumrudder", 197 | GNOLL_BREEDER = "Gnoll Breeder"; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Trait.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Trait 4 | { 5 | public const string 6 | EASILY_EXCITED = "Easily Excited", 7 | MAKING_IT_RAIN = "Making it Rain", 8 | NATURAL_BORN_SPRINTER = "Natural Born Sprinter", 9 | KIDNAPPED_A_LEPRECHAUN = "Kidnapped a Leprechaun", 10 | OVER_REACTING = "Over-Reacting", 11 | AVOIDS_PAIN = "Avoids Pain", 12 | FIRE_SALE = "Fire Sale", 13 | ICE_COLD_DEALS = "Ice Cold Deals", 14 | INSANE_ARCANE_SALE = "Insane Arcane Sale", 15 | LIGHTNING_DEALS = "Lightning Deals", 16 | BIG_SPENDER = "Big Spender", 17 | NEVER_PAYS_RETAIL = "Never Pays Retail", 18 | ONE_WITH_THE_TRAPS = "One with the Traps", 19 | OVERACHIEVER = "Overachiever", 20 | OVERPROTECTIVE = "Overprotective", 21 | RIFT_GIFT = "Rift Gift", 22 | RIFT_ROCKET = "Rift Rocket", 23 | SMASHING_DEALS = "Smashing Deals", 24 | HAS_A_TRAP_FETISH = "Has a Trap Fetish", 25 | BELIEVES_SIZE_MATTERS = "Believes Size Matters", 26 | UNCLE_KILLED_BY_GIANT = "Uncle Killed by Giant", 27 | CAT_EATEN_BY_GNOLLS = "Cat Eaten by Gnolls", 28 | BROTHER_MAULED_BY_GRIZZLIES = "Brother Mauled by Grizzlies", 29 | SISTER_EATEN_BY_AN_OGRE = "Sister Eaten by an Ogre", 30 | DOG_KILLED_BY_PRIDE_HUNTERS = "Dog Killed by Pride Hunters", 31 | GRANDMOTHER_EATEN_BY_A_TROLL = "Grandmother Eaten by a Troll", 32 | DIFFICULTY_CALMING_DOWN = "Difficulty Calming Down", 33 | AMATEUR_VETERINARIAN = "Amateur Veterinarian", 34 | SOLDIERS_TOOK_MY_HORSE = "Soldiers Took My Horse", 35 | HAS_ULTIMATE_SET_OF_TOOLS = "Has Ultimate Set of Tools", 36 | BEAR_HUGS = "Bear Hugs", 37 | HAS_A_BIG_BROTHER = "Has a Big Brother", 38 | KILLER_BUZZ = "Killer Buzz", 39 | PAGING_DR_LOVE = "Paging Dr Love", 40 | RIFT_AWAY = "Rift Away", 41 | FEELING_FIERCE = "Feeling Fierce", 42 | TRIES_TOO_HARD = "Tries Too Hard", 43 | ENJOYS_PAIN = "Enjoys Pain", 44 | EXECUTION_ADVANTAGE = "Execution Advantage", 45 | FEELS_SAFE_AT_HOME = "Feels Safe at Home", 46 | PRACTICES_WITCHCRAFT = "Practices Witchcraft", 47 | HIGH_PAIN_TOLERANCE = "High Pain Tolerance", 48 | WALKS_IT_OFF = "Walks It Off", 49 | TAKES_LESSONS_FROM_CYGNUS = "Takes Lessons from Cygnus", 50 | WALL_BUILDING_PHD = "Wall Building PhD", 51 | ATTENTION_DEFICIT = "Attention Deficit", 52 | EXTRA_PADDING = "Extra Padding", 53 | 54 | // Siege Only 55 | RESPECT_MY_AUTHORITY = "Respect My Authority", 56 | CACHE_WHISPERER = "Cache Whisperer", 57 | NEEDS_ALONE_TIME = "Needs Alone Time", 58 | LARGE_AND_IN_CHARGE = "Large and in Charge", 59 | RANSACKING_OBSESSION = "Ransacking Obsession", 60 | BORN_LEADER = "Born Leader", 61 | OH_WOW_BANDAGES = "Oh, Wow! Bandages!", 62 | SELLS_MAGIC_TO_MAGES = "Sells Magic to Mages", 63 | WORKS_SMARTER = "Works Smarter", 64 | MOTIVATES_OTHERS = "Motivates Others", 65 | OH_WOW_POTIONS = "Oh, Wow! Potions!", 66 | LIKE_YOU_STOLE_IT = "Like You Stole It", 67 | DOESNT_PLAY_WELL_WITH_OTHERS = "Doesn't Play Well with Others", 68 | MOB_MENTALITY = "Mob Mentality", 69 | SETTING_AN_EXAMPLE = "Setting an Example", 70 | STRANGE_SENSE_OF_HUMOR = "Strange Sense of Humor", 71 | NEVER_KNOCKS = "Never Knocks", 72 | REALLY_COLD_HANDS = "Really Cold Hands", 73 | HAS_FIERY_RAGE = "Has Fiery Rage", 74 | RIP_THAT_SUCKER_OPEN = "Rip That Sucker Open", 75 | ITS_MINE = "It's Mine!", 76 | NOT_SCARED_OF_STATUES = "Not Scared of Statues", 77 | IM_NOT_TOUCHING_YOU = "I'm Not Touching You", 78 | GUARDIAN_VANDALISM = "Guardian Vandalism", 79 | IS_A_WALKING_PINCUSHION = "Is a Walking Pincushion", 80 | SHOW_OFF = "Show off"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Trap.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class Trap 4 | { 5 | public const string 6 | ACID_SPRAYER = "Acid Sprayer", 7 | ARCANE_BOWLING_BALL = "Arcane Bowling Ball", 8 | ARCANE_PHASER = "Arcane Phaser", 9 | ARROW_WALL = "Arrow Wall", 10 | BGH_ARROW_WALL = "BGH Arrow Wall", 11 | CEILING_BALLISTA = "Ceiling Ballista", 12 | DRAGONS_LANCE = "Dragons Lance", 13 | BGH_CEILING_BALLISTA = "BGH Ceiling Ballista", 14 | BARRICADE = "Barricade", 15 | GREAT_WALL_BARRICADE = "Great Wall Barricade", 16 | BOOM_BARREL = "Boom Barrel", 17 | BOOM_BARREL_ROLLER = "Boom Barrel Roller", 18 | BOULDER_CHUTE = "Boulder Chute", 19 | ICICLE_IMPALER = "Icicle Impaler", 20 | BRIMSTONE = "Brimstone", 21 | COIN_FORGE = "Coin Forge", 22 | CURSED_GROUND = "Cursed Ground", 23 | DECOY = "Decoy", 24 | SPITFIRE_WALL = "Spitfire Wall", 25 | FIRE_CRACKER = "Fire Cracker", 26 | FLIP_TRAP = "Flip Trap", 27 | FLOOR_SCORCHER = "Floor Scorcher", 28 | TEMPLE_ALARM_GONG = "Temple Alarm Gong", 29 | GRINDER = "Grinder", 30 | QUARTER_POUNDER = "Quarter Pounder", 31 | HAYMAKER = "Haymaker", 32 | HEALING_WELL = "Healing Well", 33 | ICE_SHARD = "Ice Shard", 34 | ICE_VENT = "Ice Vent", 35 | LIGHTNING_ROD = "Lightning Rod", 36 | MANA_WELL = "Mana Well", 37 | SUMMONER_TRAP = "Summoner Trap", 38 | NAPHTHA_SPRAYER = "Naphtha Sprayer", 39 | OVERLOAD_TRAP = "Overload Trap", 40 | POWERUP_DAMAGE = "Powerup Damage", 41 | POUNDER = "Pounder", 42 | CONCUSSIVE_POUNDER = "Concussive Pounder", 43 | POWER_GENERATOR = "Power Generator", 44 | PUSH_TRAP = "Push Trap", 45 | SAW_OF_ARCTOS = "Saw Of Arctos", 46 | SHIELD_POWERUP = "Shield Powerup", 47 | SPEED_PAD = "Speed Pad", 48 | FLOOR_SPIKES = "Floor Spikes", 49 | SPIKE_WALL = "Spike Wall", 50 | STEAM_VENT = "Steam Vent", 51 | SWINGING_MACE = "Swinging Mace", 52 | TAR_TRAP = "Tar Trap", 53 | VISCOUS_TAR = "Viscous Tar", 54 | SHOCK_ZAPPER = "Shock Zapper", 55 | BGH_SHOCK_ZAPPER = "BGH Shock Zapper", 56 | TRIP_WIRE = "Trip Wire", 57 | WALL_BLADES = "Wall Blades", 58 | WALL_CHARGER = "Wall Charger", 59 | WEB_SPINNER = "Web Spinner", 60 | 61 | // Siege Only 62 | ANTI_MAGIC_FIELD = "Anti-Magic Field", 63 | BUFF_PYLON = "Buff Pylon", 64 | PROJECTILE_SHIELD = "Projectile Shield", 65 | HERO_SEEKER_ARROW_WALL = "Hero-Seeker Arrow Wall", 66 | HERO_SEEKER_BALLISTA = "Hero-Seeker Ballista", 67 | HERO_SEEKER_ZAPPER = "Hero-Seeker Zapper"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/TrapPart.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class TrapPart 4 | { 5 | public const string 6 | DOUBLE_TANK_ACCUMULATOR = "Double Tank Accumulator", 7 | STUNNING_ACCUMULATOR = "Stunning Accumulator", 8 | CAPACITANCE_ATTUNER = "Capacitance Attuner", 9 | DEEP_CELL_ATTUNER = "Deep Cell Attuner", 10 | REFLECTIVE_ARMOR = "Reflective Armor", 11 | REUSABLE_SHIELDING = "Reusable Shielding", 12 | REVENGE_RUNE = "Revenge Rune", 13 | BRAIDED_COIL = "Braided Coil", 14 | DISCOUNT_COIL = "Discount Coil", 15 | SELF_IMPROVING_COIL = "Self-Improving Coil", 16 | QUICK_TRAP_COMPONENTS = "Quick Trap Components", 17 | RENTAL_PARTS = "Rental Parts", 18 | STAMPED_PARTS = "Stamped Parts", 19 | SUBSIDIZED_PARTS = "Subsidized Parts", 20 | VANITY_PLATING = "Vanity Plating", 21 | BOUNTY_GENERATOR = "Bounty Generator", 22 | HEALTH_SIPHON = "Health Siphon", 23 | MANA_SIPHON = "Mana Siphon", 24 | XP_SIPHON = "XP Siphon", 25 | DOUBLE_STRUTS = "Double Struts", 26 | HERO_RUNED_FRAME = "Hero Runed Frame", 27 | CONTROLLED_RESONATOR = "Controlled Resonator", 28 | FIRE_RESONATOR = "Fire Resonator", 29 | ICE_RESONATOR = "Ice Resonator", 30 | KINETIC_RESONATOR = "Kinetic Resonator", 31 | UNCHAINED_RESONATOR = "Unchained Resonator", 32 | WEAKNESS_RESONATOR = "Debilitation Resonator", 33 | CHARGING_SPRING = "Charging Spring", 34 | DOUBLE_SPRING = "Double Spring", 35 | SHORT_SPRING = "Short Spring", 36 | AMBUSH_TRIGGER = "Ambush Trigger", 37 | COMBO_GENERATOR = "Combo Generator", 38 | CROWD_CONTROL_TRIGGER = "Crowd Control Trigger", 39 | EXECUTION_TRIGGER = "Execution Trigger", 40 | HEAT_ACTIVATED_TRIGGER = "Heat Activated Trigger", 41 | HEAVY_PRESSURE_PLATE = "Heavy Pressure Plate", 42 | KINETIC_RECLAIMER = "Kinetic Reclaimer", 43 | LIGHT_PRESSURE_PLATE = "Light Pressure Plate"; 44 | } 45 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/TrapPartSlot.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class TrapPartSlot 4 | { 5 | public const string ACCUMULATOR = "Accumulator"; 6 | public const string ATTUNER = "Attuner"; 7 | public const string CLADDING = "Cladding"; 8 | public const string COIL = "Coil"; 9 | public const string COMPONENTS = "Components"; 10 | public const string FIELD = "Field"; 11 | public const string FRAME = "Frame"; 12 | public const string RESONATOR = "Resonator"; 13 | public const string SPRING = "Spring"; 14 | public const string TRIGGER = "Trigger"; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/Wave.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public class Wave 4 | { 5 | public const string 6 | 7 | // Siege Only 8 | LVL1_2_DWARF_SHAMANS = "2 Dwarf Shamans", 9 | LVL1_2_GNOLL_HUNTERS = "2 Gnoll Hunters", 10 | LVL1_2_GRIZZLY_SHIELDBEARERS = "2 Grizzly Shieldbearers", 11 | LVL1_2_GRIZZLY_BEARS = "2 Grizzly Bears", 12 | LVL1_4_CROSSBOW_ORCS = "4 Crossbow Orcs", 13 | LVL1_5_LIGHT_SOLDIERS = "5 Light Soldiers", 14 | LVL1_9_KOBOLDS = "9 Kobolds", 15 | LVL1_9_LIGHT_ORCS = "9 Light Orcs", 16 | 17 | LVL2_14_KOBOLDS = "14 Kobolds", 18 | LVL2_14_LIGHT_ORCS = "14 Light Orcs", 19 | LVL2_2_CYCLOPS_SHAMANS = "2 Cyclops Shamans", 20 | LVL2_3_DWARF_SHAMANS = "3 Dwarf Shamans", 21 | LVL2_3_GNOLL_HUNTERS = "3 Gnoll Hunters", 22 | LVL2_3_GRIZZLY_BEARS = "3 Grizzly Bears", 23 | LVL2_3_GRIZZLY_SHIELDBEARERS = "3 Grizzly Shieldbearers", 24 | LVL2_4_GNOME_TINKERERS = "4 Gnome Tinkerers", 25 | LVL2_6_CROSSBOW_ORCS = "6 Crossbow Orcs", 26 | LVL2_8_LIGHT_SOLDIERS = "8 Light Soldiers", 27 | 28 | LVL3_2_GIANTS = "2 Giants", 29 | LVL3_2_HOBGOBLIN_SHAMANS = "2 Hobgoblin Shamans", 30 | LVL3_2_PRIDE_HUNTERS = "2 Pride Hunters", 31 | LVL3_2_TROLL_SHIELDBEARERS = "2 Troll Shieldbearers", 32 | LVL3_3_CYCLOPS_SHAMANS = "3 Cyclops Shamans", 33 | LVL3_3_OGRES = "3 Ogres", 34 | LVL3_4_HUMAN_CROSSBOWMEN = "4 Human Crossbowmen", 35 | LVL3_5_MEDIUM_SOLDIERS = "5 Medium Soldiers", 36 | LVL3_9_MEDIUM_ORCS = "9 Medium Orcs", 37 | LVL3_9_SATYRS = "9 Satyrs", 38 | 39 | LVL4_14_MEDIUM_ORCS = "14 Medium Orcs", 40 | LVL4_14_SATYRS = "14 Satyrs", 41 | LVL4_2_FIRE_MAGES = "2 Fire Mages", 42 | LVL4_2_TROLLS = "2 Trolls", 43 | LVL4_3_GIANTS = "3 Giants", 44 | LVL4_3_HOBGOBLIN_SHAMANS = "3 Hobgoblin Shamans", 45 | LVL4_3_PRIDE_HUNTERS = "3 Pride Hunters", 46 | LVL4_3_TROLL_SHIELDBEARERS = "3 Troll Shieldbearers", 47 | LVL4_5_OGRES = "5 Ogres", 48 | LVL4_6_HUMAN_CROSSBOWMEN = "6 Human Crossbowmen", 49 | LVL4_8_MEDIUM_SOLDIERS = "8 Medium Soldiers", 50 | LVL4_9_GNOME_TINKERERS = "9 Gnome Tinkerers", 51 | 52 | BOSS_ORFUM_MOUNTAIN_TROLL = "Orfum, Mountain Troll", 53 | BOSS_OAKENTOES_DWARF_SHAMAN = "Oakentoes, Dwarf Shaman", 54 | BOSS_SAWBONES_HOBGOBLIN_SHAMAN = "Sawbones, Hobgoblin Shaman", 55 | BOSS_BULGOD_ARMORED_OGRE = "Bulgod, Armored Ogre", 56 | BOSS_URZA_FIRE_LORD = "Urza the Fire Lord", 57 | BOSS_GAEADIN_EARTH_LORD = "Gaeadin the Earth Lord", 58 | BOSS_SWIFTYHOOVES = "Swiftyhooves", 59 | BOSS_TIKLIK_HOBGOBLIN_SHAMAN = "TikLik, Hobgoblin Shaman", 60 | BOSS_VITALITY = "Vitality", 61 | BOSS_GATEBREAKER = "Gatebreaker", 62 | BOSS_LYZANDER_CULT_RESURRECTOR = "Lyzander, Cult Resurrector", 63 | BOSS_GRENWALDE_NATURE_LORD = "Grenwalde the Nature Lord", 64 | BOSS_TUBIFORE = "Tubifore"; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Entities/Names/WeeklyChallenge.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Names 2 | { 3 | public static class WeeklyChallenge 4 | { 5 | public const string 6 | ALMS_FOR_THE_POOR = "Alms for the Poor", 7 | DO_THE_TRAP_SHUFFLE = "Do The Trap Shuffle", 8 | DODGE_CITY = "Dodge City", 9 | DOUBLETS = "Doublets!", 10 | FREEZED_TO_MEET_YOU = "Freezed to Meet You", 11 | FUN_WITH_PHYSICS = "Fun with Physics", 12 | GOTTA_GO_FAST = "Gotta Go Fast", 13 | GRAND_FINALE = "Grand Finale", 14 | IM_THE_BOSS_NOW = "I'm the Boss Now", 15 | INFERIORITY_COMPLEX = "Inferiority Complex", 16 | KEEP_YOUR_DISTANCE = "Keep Your Distance", 17 | LIBRARY_RINTH = "Library-rinth", 18 | MEMORY_LANES = "Memory Lanes", 19 | OFF_THE_CHAIN = "Off the Chain!", 20 | ONE_AND_DONE = "One and Done", 21 | PLAYING_WITH_FIRE = "Playing With Fire", 22 | PURPLE_MAZE = "Purple Maze", 23 | RIFT_INSTABILITY = "Rift Instability", 24 | ROLLING_IN_IT = "Rolling in it", 25 | THE_MERCS_LITTER = "The Merc's Litter", 26 | VANISHING_ACT = "Vanishing Act", 27 | WE_CAN_REBUILD_IT = "We Can Rebuild It", 28 | YOUR_OWN_DEVICES = "Your Own Devices"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Forms/ComboBoxHelper.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Model; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Drawing; 5 | using System.Windows.Forms; 6 | 7 | public class ComboBoxHelper 8 | { 9 | private readonly ToolTip _toolTip; 10 | private readonly Dictionary _items; 11 | private readonly Func _getToolTipText; 12 | private readonly Action _drawItemShape; 13 | 14 | public ComboBoxHelper(Dictionary items, Func getToolTipText, Action drawItemShape) 15 | { 16 | this._toolTip = new ToolTip(); 17 | this._items = items; 18 | this._getToolTipText = getToolTipText; 19 | this._drawItemShape = drawItemShape; 20 | } 21 | 22 | public void InitializeComboBox(ComboBox comboBox) 23 | { 24 | comboBox.DrawMode = DrawMode.OwnerDrawFixed; 25 | comboBox.DrawItem += ComboBox_DrawItem; 26 | comboBox.DropDownClosed += ComboBox_DropDownClosed; 27 | } 28 | 29 | private void ComboBox_DrawItem(object sender, DrawItemEventArgs eventDrawItem) 30 | { 31 | ComboBox comboBox = (ComboBox)sender; 32 | 33 | if (eventDrawItem.Index < 0) { return; } 34 | 35 | string itemName = comboBox.GetItemText(comboBox.Items[eventDrawItem.Index]); 36 | if (!_items.TryGetValue(itemName, out T item)) { return; } 37 | 38 | // Draw the background 39 | if ((eventDrawItem.State & DrawItemState.Selected) == DrawItemState.Selected) 40 | { 41 | eventDrawItem.Graphics.FillRectangle(SystemBrushes.Highlight, eventDrawItem.Bounds); 42 | } 43 | else 44 | { 45 | eventDrawItem.Graphics.FillRectangle(SystemBrushes.Window, eventDrawItem.Bounds); 46 | } 47 | 48 | // Draw the shape with the appropriate item 49 | _drawItemShape?.Invoke(eventDrawItem.Graphics, eventDrawItem.Bounds, item); 50 | 51 | // Draw the item text 52 | using (Brush brush = new SolidBrush((eventDrawItem.State & DrawItemState.Selected) == DrawItemState.Selected ? SystemColors.HighlightText : SystemColors.ControlText)) 53 | { 54 | int iXPadding = _drawItemShape != null ? 25 : 0; 55 | eventDrawItem.Graphics.DrawString(itemName, eventDrawItem.Font, brush, eventDrawItem.Bounds.X + iXPadding, eventDrawItem.Bounds.Y); 56 | } 57 | 58 | if ((eventDrawItem.State & DrawItemState.Selected) == DrawItemState.Selected) 59 | { 60 | // To show the tool tips only when the ComboBox is open 61 | if ((eventDrawItem.State & DrawItemState.ComboBoxEdit) != DrawItemState.ComboBoxEdit) 62 | { 63 | _toolTip.Show(_getToolTipText(item), comboBox, eventDrawItem.Bounds.Right, eventDrawItem.Bounds.Bottom); 64 | } 65 | 66 | // Focus is a low-level method intended primarily for custom control 67 | // authors. Instead, application programmers should use the Select 68 | // method or the ActiveControl property for child controls, or the 69 | // Activate method for forms. 70 | // https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.control.focus?view=windowsdesktop-8.0&redirectedfrom=MSDN#System_Windows_Forms_Control_Focus 71 | eventDrawItem.DrawFocusRectangle(); 72 | } 73 | } 74 | 75 | private void ComboBox_DropDownClosed(object sender, EventArgs eventDropDown) 76 | { 77 | _toolTip.Hide((Control)sender); 78 | } 79 | } 80 | 81 | public class TraitComboBoxHelper : ComboBoxHelper where T : Trait 82 | { 83 | public TraitComboBoxHelper(Dictionary traits) 84 | : base(traits, GetToolTipText, DrawItemShape) 85 | { 86 | } 87 | 88 | private static string GetToolTipText(T trait) 89 | { 90 | return $"{trait.Name}\n\n{trait.Description}\n\nMatching Bonus: {trait.MatchingBonusTrait.Description}"; 91 | } 92 | 93 | private static void DrawItemShape(Graphics g, Rectangle bounds, T trait) 94 | { 95 | Color shapeColor; 96 | Action drawShape; 97 | 98 | switch (trait.MatchingSlot) 99 | { 100 | case Trait.TRIANGLE_BONUS_SLOT: 101 | shapeColor = Color.Yellow; 102 | drawShape = DrawTriangle; 103 | break; 104 | case Trait.DIAMOND_BONUS_SLOT: 105 | shapeColor = Color.Blue; 106 | drawShape = DrawDiamond; 107 | break; 108 | case Trait.PENTAGON_BONUS_SLOT: 109 | shapeColor = Color.Green; 110 | drawShape = DrawPentagon; 111 | break; 112 | default: 113 | shapeColor = Color.Black; 114 | drawShape = DrawRectangle; // Default shape 115 | break; 116 | } 117 | 118 | using (Brush brush = new SolidBrush(shapeColor)) 119 | { 120 | drawShape(g, bounds); 121 | } 122 | } 123 | 124 | private static void DrawTriangle(Graphics g, Rectangle bounds) 125 | { 126 | Point[] points = new Point[] 127 | { 128 | new(bounds.X + 10, bounds.Y + bounds.Height - 5), 129 | new(bounds.X + 20, bounds.Y + bounds.Height - 5), 130 | new(bounds.X + 15, bounds.Y + bounds.Height - 15) 131 | }; 132 | g.FillPolygon(Brushes.Gold, points); 133 | } 134 | 135 | private static void DrawDiamond(Graphics g, Rectangle bounds) 136 | { 137 | Point[] points = new Point[] 138 | { 139 | new(bounds.X + 15, bounds.Y + bounds.Height - 15), 140 | new(bounds.X + 20, bounds.Y + bounds.Height - 10), 141 | new(bounds.X + 15, bounds.Y + bounds.Height - 5), 142 | new(bounds.X + 10, bounds.Y + bounds.Height - 10) 143 | }; 144 | g.FillPolygon(Brushes.LightSkyBlue, points); 145 | } 146 | 147 | private static void DrawPentagon(Graphics g, Rectangle bounds) 148 | { 149 | Point[] points = new Point[] 150 | { 151 | new(bounds.X + 15, bounds.Y + bounds.Height - 15), 152 | new(bounds.X + 20, bounds.Y + bounds.Height - 10), 153 | new(bounds.X + 18, bounds.Y + bounds.Height - 5), 154 | new(bounds.X + 12, bounds.Y + bounds.Height - 5), 155 | new(bounds.X + 10, bounds.Y + bounds.Height - 10) 156 | }; 157 | g.FillPolygon(Brushes.LightGreen, points); 158 | } 159 | 160 | private static void DrawRectangle(Graphics g, Rectangle bounds) 161 | { 162 | g.FillRectangle(Brushes.Gray, bounds); 163 | } 164 | } 165 | 166 | 167 | public class SlotItemComboBoxHelper : ComboBoxHelper 168 | { 169 | public SlotItemComboBoxHelper(Dictionary slotitems) 170 | : base(slotitems, GetToolTipText, null) 171 | { 172 | } 173 | 174 | private static string GetToolTipText(SlotItem slotItem) 175 | { 176 | return $"{slotItem.Name}\n\n{slotItem.Description}"; 177 | } 178 | } 179 | 180 | public class SiegeSlotItemComboBoxHelper : ComboBoxHelper 181 | { 182 | public SiegeSlotItemComboBoxHelper(Dictionary slotitems) 183 | : base(slotitems, GetToolTipText, null) 184 | { 185 | } 186 | 187 | private static string GetToolTipText(SlotItem slotItem) 188 | { 189 | return $"{slotItem.Name}\n\n{slotItem.SiegeDescription}"; 190 | } 191 | } 192 | 193 | public class TrapPartComboBoxHelper : ComboBoxHelper 194 | { 195 | public TrapPartComboBoxHelper(Dictionary trapParts) 196 | : base(trapParts, GetToolTipText, null) 197 | { 198 | } 199 | 200 | private static string GetToolTipText(TrapPart trapPart) 201 | { 202 | return $"{trapPart.Name}\n\n{trapPart.Description}"; 203 | } 204 | } 205 | 206 | public class GuardianComboBoxHelper : ComboBoxHelper 207 | { 208 | public GuardianComboBoxHelper(Dictionary guardians) 209 | : base(guardians, GetToolTipText, null) 210 | { 211 | } 212 | 213 | private static string GetToolTipText(Guardian guardian) 214 | { 215 | return $"{guardian.Name}\n\n{guardian.AuraText}"; 216 | } 217 | } 218 | 219 | public class ConsumableComboBoxHelper : ComboBoxHelper 220 | { 221 | public ConsumableComboBoxHelper(Dictionary consumables) 222 | : base(consumables, GetToolTipText, null) 223 | { 224 | } 225 | 226 | private static string GetToolTipText(Consumable consumable) 227 | { 228 | return $"{consumable.Name}\n\n{consumable.Description}"; 229 | } 230 | } 231 | 232 | public class RoleComboBoxHelper : ComboBoxHelper 233 | { 234 | public RoleComboBoxHelper(Dictionary role) 235 | : base(role, GetToolTipText, null) 236 | { 237 | } 238 | 239 | private static string GetToolTipText(SiegeRole role) 240 | { 241 | return $"{role.Name}\n\n{role.SiegeDescription}\n\n{role.Effect}"; 242 | } 243 | } 244 | public class WaveComboBoxHelper : ComboBoxHelper 245 | { 246 | public WaveComboBoxHelper(Dictionary waves) 247 | : base(waves, GetToolTipText, null) 248 | { 249 | } 250 | 251 | private static string GetToolTipText(Wave wave) 252 | { 253 | return $"{wave.Name}\n\n{wave.Description}\n\n{wave.KeywordText}\n{wave.KeywordDescription}"; 254 | } 255 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Forms/FolderPicker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.InteropServices.ComTypes; 7 | 8 | public class FolderPicker 9 | { 10 | private readonly List _resultPaths = []; 11 | private readonly List _resultNames = []; 12 | 13 | public IReadOnlyList ResultPaths => _resultPaths; 14 | public IReadOnlyList ResultNames => _resultNames; 15 | public string ResultPath => ResultPaths.FirstOrDefault(); 16 | public string ResultName => ResultNames.FirstOrDefault(); 17 | public virtual string InputPath { get; set; } 18 | public virtual bool ForceFileSystem { get; set; } 19 | public virtual bool Multiselect { get; set; } 20 | public virtual string Title { get; set; } 21 | public virtual string OkButtonLabel { get; set; } 22 | public virtual string FileNameLabel { get; set; } 23 | 24 | protected virtual int SetOptions(int options) 25 | { 26 | if (ForceFileSystem) 27 | { 28 | options |= (int)FOS.FOS_FORCEFILESYSTEM; 29 | } 30 | 31 | if (Multiselect) 32 | { 33 | options |= (int)FOS.FOS_ALLOWMULTISELECT; 34 | } 35 | return options; 36 | } 37 | 38 | // for all .NET 39 | public virtual bool? ShowDialog(IntPtr owner, bool throwOnError = false) 40 | { 41 | var dialog = (IFileOpenDialog)new FileOpenDialog(); 42 | if (!string.IsNullOrEmpty(InputPath)) 43 | { 44 | if (CheckHr(SHCreateItemFromParsingName(InputPath, null, typeof(IShellItem).GUID, out var item), throwOnError) != 0) 45 | { 46 | return null; 47 | } 48 | 49 | dialog.SetFolder(item); 50 | } 51 | 52 | var options = FOS.FOS_PICKFOLDERS; 53 | options = (FOS)SetOptions((int)options); 54 | dialog.SetOptions(options); 55 | 56 | if (Title != null) 57 | { 58 | dialog.SetTitle(Title); 59 | } 60 | 61 | if (OkButtonLabel != null) 62 | { 63 | dialog.SetOkButtonLabel(OkButtonLabel); 64 | } 65 | 66 | if (FileNameLabel != null) 67 | { 68 | dialog.SetFileName(FileNameLabel); 69 | } 70 | 71 | if (owner == IntPtr.Zero) 72 | { 73 | owner = Process.GetCurrentProcess().MainWindowHandle; 74 | if (owner == IntPtr.Zero) 75 | { 76 | owner = GetDesktopWindow(); 77 | } 78 | } 79 | 80 | var hr = dialog.Show(owner); 81 | if (hr == ERROR_CANCELLED) 82 | { 83 | return null; 84 | } 85 | 86 | if (CheckHr(hr, throwOnError) != 0) 87 | { 88 | return null; 89 | } 90 | 91 | if (CheckHr(dialog.GetResults(out var items), throwOnError) != 0) 92 | { 93 | return null; 94 | } 95 | 96 | items.GetCount(out var count); 97 | for (var i = 0; i < count; i++) 98 | { 99 | items.GetItemAt(i, out var item); 100 | CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, out var path), throwOnError); 101 | CheckHr(item.GetDisplayName(SIGDN.SIGDN_DESKTOPABSOLUTEEDITING, out var name), throwOnError); 102 | if (path != null || name != null) 103 | { 104 | _resultPaths.Add(path); 105 | _resultNames.Add(name); 106 | } 107 | } 108 | return true; 109 | } 110 | 111 | private static int CheckHr(int hr, bool throwOnError) 112 | { 113 | if (hr != 0 && throwOnError) 114 | { 115 | Marshal.ThrowExceptionForHR(hr); 116 | } 117 | 118 | return hr; 119 | } 120 | 121 | [DllImport("shell32")] 122 | private static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv); 123 | 124 | [DllImport("user32")] 125 | private static extern IntPtr GetDesktopWindow(); 126 | 127 | #pragma warning disable IDE1006 // Naming Styles 128 | private const int ERROR_CANCELLED = unchecked((int)0x800704C7); 129 | #pragma warning restore IDE1006 // Naming Styles 130 | 131 | [ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")] // CLSID_FileOpenDialog 132 | private class FileOpenDialog { } 133 | 134 | [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 135 | private interface IFileOpenDialog 136 | { 137 | [PreserveSig] int Show(IntPtr parent); // IModalWindow 138 | [PreserveSig] int SetFileTypes(); // not fully defined 139 | [PreserveSig] int SetFileTypeIndex(int iFileType); 140 | [PreserveSig] int GetFileTypeIndex(out int piFileType); 141 | [PreserveSig] int Advise(); // not fully defined 142 | [PreserveSig] int Unadvise(); 143 | [PreserveSig] int SetOptions(FOS fos); 144 | [PreserveSig] int GetOptions(out FOS pfos); 145 | [PreserveSig] int SetDefaultFolder(IShellItem psi); 146 | [PreserveSig] int SetFolder(IShellItem psi); 147 | [PreserveSig] int GetFolder(out IShellItem ppsi); 148 | [PreserveSig] int GetCurrentSelection(out IShellItem ppsi); 149 | [PreserveSig] int SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName); 150 | [PreserveSig] int GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); 151 | [PreserveSig] int SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle); 152 | [PreserveSig] int SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText); 153 | [PreserveSig] int SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel); 154 | [PreserveSig] int GetResult(out IShellItem ppsi); 155 | [PreserveSig] int AddPlace(IShellItem psi, int alignment); 156 | [PreserveSig] int SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); 157 | [PreserveSig] int Close(int hr); 158 | [PreserveSig] int SetClientGuid(); // not fully defined 159 | [PreserveSig] int ClearClientData(); 160 | [PreserveSig] int SetFilter([MarshalAs(UnmanagedType.IUnknown)] object pFilter); 161 | [PreserveSig] int GetResults(out IShellItemArray ppenum); 162 | [PreserveSig] int GetSelectedItems([MarshalAs(UnmanagedType.IUnknown)] out object ppsai); 163 | } 164 | 165 | [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 166 | private interface IShellItem 167 | { 168 | [PreserveSig] int BindToHandler(); // not fully defined 169 | [PreserveSig] int GetParent(); // not fully defined 170 | [PreserveSig] int GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName); 171 | [PreserveSig] int GetAttributes(); // not fully defined 172 | [PreserveSig] int Compare(); // not fully defined 173 | } 174 | 175 | [ComImport, Guid("b63ea76d-1f85-456f-a19c-48159efa858b"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 176 | private interface IShellItemArray 177 | { 178 | [PreserveSig] int BindToHandler(); // not fully defined 179 | [PreserveSig] int GetPropertyStore(); // not fully defined 180 | [PreserveSig] int GetPropertyDescriptionList(); // not fully defined 181 | [PreserveSig] int GetAttributes(); // not fully defined 182 | [PreserveSig] int GetCount(out int pdwNumItems); 183 | [PreserveSig] int GetItemAt(int dwIndex, out IShellItem ppsi); 184 | [PreserveSig] int EnumItems(); // not fully defined 185 | } 186 | 187 | #pragma warning disable CA1712 // Do not prefix enum values with type name 188 | private enum SIGDN : uint 189 | { 190 | SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, 191 | SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, 192 | SIGDN_FILESYSPATH = 0x80058000, 193 | SIGDN_NORMALDISPLAY = 0, 194 | SIGDN_PARENTRELATIVE = 0x80080001, 195 | SIGDN_PARENTRELATIVEEDITING = 0x80031001, 196 | SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8007c001, 197 | SIGDN_PARENTRELATIVEPARSING = 0x80018001, 198 | SIGDN_URL = 0x80068000 199 | } 200 | 201 | [Flags] 202 | private enum FOS 203 | { 204 | FOS_OVERWRITEPROMPT = 0x2, 205 | FOS_STRICTFILETYPES = 0x4, 206 | FOS_NOCHANGEDIR = 0x8, 207 | FOS_PICKFOLDERS = 0x20, 208 | FOS_FORCEFILESYSTEM = 0x40, 209 | FOS_ALLNONSTORAGEITEMS = 0x80, 210 | FOS_NOVALIDATE = 0x100, 211 | FOS_ALLOWMULTISELECT = 0x200, 212 | FOS_PATHMUSTEXIST = 0x800, 213 | FOS_FILEMUSTEXIST = 0x1000, 214 | FOS_CREATEPROMPT = 0x2000, 215 | FOS_SHAREAWARE = 0x4000, 216 | FOS_NOREADONLYRETURN = 0x8000, 217 | FOS_NOTESTFILECREATE = 0x10000, 218 | FOS_HIDEMRUPLACES = 0x20000, 219 | FOS_HIDEPINNEDPLACES = 0x40000, 220 | FOS_NODEREFERENCELINKS = 0x100000, 221 | FOS_OKBUTTONNEEDSINTERACTION = 0x200000, 222 | FOS_DONTADDTORECENT = 0x2000000, 223 | FOS_FORCESHOWHIDDEN = 0x10000000, 224 | FOS_DEFAULTNOMINIMODE = 0x20000000, 225 | FOS_FORCEPREVIEWPANEON = 0x40000000, 226 | FOS_SUPPORTSTREAMABLEITEMS = unchecked((int)0x80000000) 227 | } 228 | #pragma warning restore CA1712 // Do not prefix enum values with type name 229 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Forms/InputValidator.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Model; 2 | using System.Text.RegularExpressions; 3 | 4 | public static class InputValidator 5 | { 6 | public static (bool isValid, string errorMessage) ValidateLoadoutName(string loadoutName) 7 | { 8 | if (loadoutName.Length < 3) 9 | { 10 | return (false, "Loadout name must be at least 3 characters long."); 11 | } 12 | 13 | if (loadoutName.Length > 32) 14 | { 15 | return (false, "Loadout name must be at most 32 characters long."); 16 | } 17 | 18 | if (!Regex.IsMatch(loadoutName, "^[a-zA-Z0-9 _-]+$")) 19 | { 20 | return (false, "Loadout name must be alphanumeric and can include spaces, hyphens, and underscores."); 21 | } 22 | 23 | return (true, string.Empty); 24 | } 25 | 26 | public static (bool isValid, string errorMessage) ValidatePlayerName(string playerName) 27 | { 28 | if (playerName.Length < 3) 29 | { 30 | return (false, "Player name must be at least 3 characters long."); 31 | } 32 | 33 | if (playerName.Length > 12) 34 | { 35 | return (false, "Player name must be at most 12 characters long."); 36 | } 37 | 38 | if (!Regex.IsMatch(playerName, "^[a-zA-Z0-9]+$")) 39 | { 40 | return (false, "Player name must be alphanumeric."); 41 | } 42 | 43 | return (true, string.Empty); 44 | } 45 | 46 | public static (bool isValid, string errorMessage) ValidateLoadoutCode(string loadoutCode) 47 | { 48 | try 49 | { 50 | SurvivalLoadout loadout = new(); 51 | loadout.Decode(loadoutCode); 52 | return (true, string.Empty); 53 | } 54 | catch 55 | { 56 | return (false, "Invalid loadout code."); 57 | } 58 | } 59 | 60 | public static (bool isValid, string errorMessage) ValidateSiegeLoadoutCode(string loadoutCode) 61 | { 62 | try 63 | { 64 | SiegeLoadout loadout = new(); 65 | loadout.Decode(loadoutCode); 66 | return (true, string.Empty); 67 | } 68 | catch 69 | { 70 | return (false, "Invalid loadout code."); 71 | } 72 | } 73 | 74 | public static (bool isValid, string errorMessage) ValidateIpAddress(string ipAddress) 75 | { 76 | string pattern = @"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." + 77 | @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." + 78 | @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\." + 79 | @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; 80 | 81 | if (Regex.IsMatch(ipAddress, pattern)) 82 | { 83 | return (true, string.Empty); 84 | } 85 | 86 | return (false, "Invalid IP address format."); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Forms/LauncherMainForm.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | 144 125 | 126 | 127 | 128 | 129 | iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO 130 | vAAADrwBlbxySQAAAcJJREFUSEvd1bsvg1EYBvBWQhoimlhqICEsjVjEfyAGQw1WgskqRGJlJKSbS8J/ 131 | YO3okjCZDBJ3CSJuQRq6tC7PU8/H8fYmaaee5Jd83/u855y236W+8h6R0UQ9NOo0YzBjj07/NzAhBCOw 132 | DtdwCC3AeqvwuBmOgD3s5ZyQlsk+1PQAn8YN3MGL3Kpm++5hWMv9HQj4yZ7UWAyu0aZlfweKq05Tsda0 133 | 7PdAoR0STkOxuFaHlk9vsOCEpRL1Fq+DMycolXMIcoNup+h6hijMw6NqLl5MfnPKdXP0cINpU6QP6E9/ 134 | RQwc90JSGb1Dn2LmEdXcNWiGYcwUiQ9XQPPZUwH7yugAKhUzr1LNXYNiDI9NkS6gVvPZEwA+tV5+CtWK 135 | mdeo5q5BJwz5W9uApjSfPWMmo0nFzCdM5nlmmDJFD6/DNmxAtt+X+RZsQracUvk2KIUkN4ibIvEe3jO1 136 | fNjL62brcW4wBPbNyA3GYQD4LOwAH0a+RflWZb4LzAaB18BuwDWHvIvUBEvwBm5Tp3I/BKFBeOxX1gXu 137 | nFdYhMw/KhTDMAdXsKxywYHeFbiEWQirnHugiZ/w5z4vNNjLOTotq+HzfQGlv6ywArnnwAAAAABJRU5E 138 | rkJggg== 139 | 140 | 141 | 142 | 140, 17 143 | 144 | 145 | 146 | How it works? 147 | - Peer to peer, LAN supported 148 | - Online multiplayer step by step guide to setup in our Discord 149 | - Play online using VPN software such as Radmin 150 | 151 | 152 | Looking for group? Join our discord community! 153 | - We have dedicated roles and channels for LFG and matchmaking 154 | 155 | 156 | 157 | 158 | How it works? 159 | - Peer to peer, LAN supported 160 | - Online multiplayer step by step guide to setup in our Discord 161 | - Play online using VPN software such as Radmin 162 | 163 | 164 | Looking for group? Join our discord community! 165 | - We have dedicated roles and channels for LFG and matchmaking 166 | 167 | 168 | 169 | 170 | How it works? 171 | - Peer to peer, LAN supported 172 | - Online multiplayer step by step guide to setup in our Discord 173 | - Play online using VPN software such as Radmin 174 | 175 | 176 | Looking for group? Join our discord community! 177 | - We have dedicated roles and channels for LFG and matchmaking 178 | 179 | 180 | 181 | 182 | How it works? 183 | - Peer to peer, LAN supported 184 | - Online multiplayer step by step guide to setup in our Discord 185 | - Play online using VPN software such as Radmin 186 | 187 | 188 | Looking for group? Join our discord community! 189 | - We have dedicated roles and channels for LFG and matchmaking 190 | 191 | 192 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameFiles/Config/ConfigFile.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Utils; 2 | using System.IO; 3 | 4 | namespace SingleplayerLauncher.GameFiles 5 | { 6 | internal class ConfigFile 7 | { 8 | private readonly string path; // filename included 9 | public IniFile data; 10 | 11 | public ConfigFile(string filePath, bool newFile = false) 12 | { 13 | path = filePath; 14 | data = new IniFile(); 15 | 16 | if (!newFile && File.Exists(path)) 17 | { 18 | data.Load(path); 19 | } 20 | } 21 | 22 | public void Write() 23 | { 24 | data.Save(path); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameFiles/Config/DefaultGame.cs: -------------------------------------------------------------------------------- 1 | using ProjectRechained.Entities; 2 | using SingleplayerLauncher.Model; 3 | using SingleplayerLauncher.Utils; 4 | using System; 5 | using System.IO; 6 | using System.Windows.Forms; 7 | 8 | namespace SingleplayerLauncher.GameFiles 9 | { 10 | public static class DefaultGame 11 | { 12 | // TODO: make singleton 13 | 14 | private const string RDisplayColorInfoSection = "SpitfireGame.RDisplayColorInfo"; 15 | private const string RGameReplicationInfoSection = "SpitfireGame.RGameReplicationInfo"; 16 | private const string RHUDBaseSection = "SpitfireGame.RHUDBase"; 17 | private const string RGameStateSection = "SpitfireGame.RGameState"; 18 | 19 | private const string GameReplicationInfoKeyGameMode = "DefaultOfflineDifficulty"; 20 | private const string GameReplicationInfoKeyMapLevel = "DefaultOfflineSuggestedLevel"; 21 | private const string GameReplicationInfoKeyPlayerLevel = "DefaultOfflinePlayerLevel"; 22 | private const string GameReplicationInfoKeyPlayerCount = "PlayerCountOverride"; 23 | private const string GameReplicationInfoKeyPauseTimerInSeconds = "DefaultOfflinePauseTimerDurationInSeconds"; 24 | private const string RHUDBaseKeyShowFlyoffsForTrapDamage = "ShowFlyoffsForTrapDamage"; 25 | private const string DebugBotPlayerIDs = ".DebugBotPlayerIDs"; 26 | 27 | private const string DefaultPauseTimerInSeconds = "999999"; 28 | private const int MAX_BOT_COUNT = 10; 29 | 30 | public static void ApplySurvival() 31 | { 32 | _ = GameInfo.Instance.SurvivalBattleground ?? throw new ArgumentNullException(nameof(Battleground), "Mandatory parameter"); 33 | _ = GameInfo.Instance.SurvivalBattleground.Difficulty ?? throw new ArgumentNullException(nameof(Battleground.Difficulty), "Mandatory parameter"); 34 | _ = Mods.Mods.ShowTrapDamageFlyoffs ?? throw new ArgumentNullException(nameof(Mods.Mods.ShowTrapDamageFlyoffs), "Mandatory parameter"); 35 | _ = Mods.Mods.AccountLevelOverride ?? throw new ArgumentNullException(nameof(Mods.Mods.AccountLevelOverride), "Mandatory parameter"); 36 | 37 | ConfigFile defaultGame = new(Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_DEFAULT_GAME_FILENAME)); 38 | IniFile data = defaultGame.data; 39 | 40 | data.UpdateEntry(RHUDBaseSection, RHUDBaseKeyShowFlyoffsForTrapDamage, Mods.Mods.ShowTrapDamageFlyoffs.IsEnabled.ToString()); 41 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyGameMode, GameInfo.Instance.SurvivalBattleground.GameMode.Id.ToString()); 42 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyMapLevel, GameInfo.Instance.SurvivalBattleground.Difficulty.EnemyLevel.ToString()); 43 | int accountLevel = Mods.Mods.AccountLevelOverride.IsEnabled ? Mods.Mods.AccountLevelOverride.Value : GameInfo.Instance.SurvivalBattleground.Difficulty.AccountLevel; 44 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyPlayerLevel, accountLevel.ToString()); 45 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyPlayerCount, GameInfo.Instance.SurvivalBattleground.Difficulty.PlayerCount.ToString()); 46 | 47 | defaultGame.Write(); 48 | } 49 | 50 | public static void ApplySurvivalSettings() 51 | { 52 | _ = Mods.Mods.ShowTrapDamageFlyoffs ?? throw new ArgumentNullException(nameof(Mods.Mods.ShowTrapDamageFlyoffs), "Mandatory parameter"); 53 | 54 | ConfigFile defaultGame = new(Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_DEFAULT_GAME_FILENAME)); 55 | IniFile data = defaultGame.data; 56 | 57 | data.UpdateEntry(RHUDBaseSection, RHUDBaseKeyShowFlyoffsForTrapDamage, Mods.Mods.ShowTrapDamageFlyoffs.IsEnabled.ToString()); 58 | 59 | defaultGame.Write(); 60 | } 61 | 62 | public static void ApplySiegeBots(bool isSiegeAllyBots = false) 63 | { 64 | _ = GameConfig.Instance.SiegeEnemyTeamAsBots ? true : throw new ArgumentNullException(nameof(GameConfig.Instance.SiegeEnemyTeamAsBots), "Mandatory parameter"); 65 | _ = GameConfig.Instance.SiegeBotDifficulty ?? throw new ArgumentNullException(nameof(GameConfig.Instance.SiegeBotDifficulty), "Mandatory parameter"); 66 | 67 | ConfigFile defaultGame = new(Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_DEFAULT_GAME_FILENAME)); 68 | IniFile data = defaultGame.data; 69 | 70 | String botDifficultyName = GameConfig.Instance.SiegeBotDifficulty; 71 | BotDifficulty botDifficulty = BotDifficulty.BotDifficultiesByName[botDifficultyName]; 72 | 73 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyGameMode, botDifficulty.BotLevel.ToString()); 74 | 75 | data.RemoveSection(RGameStateSection); 76 | 77 | int botCount = 0; 78 | foreach (SiegeLoadout botLoadout in botDifficulty.botLoadouts) 79 | { 80 | data.UpdateEntry(RGameStateSection, DebugBotPlayerIDs, botLoadout.PlayerName, botCount); 81 | botCount++; 82 | } 83 | 84 | if (isSiegeAllyBots) 85 | { 86 | foreach (SiegeLoadout allyBotLoadout in GameInfo.Instance.AllyBotsLoadouts) 87 | { 88 | data.UpdateEntry(RGameStateSection, DebugBotPlayerIDs, allyBotLoadout.PlayerName, botCount); 89 | botCount++; 90 | } 91 | } 92 | 93 | defaultGame.Write(); 94 | } 95 | 96 | private static void ClearBotsConfiguration(IniFile data) 97 | { 98 | for (int i = 0; i < MAX_BOT_COUNT; i++) 99 | { 100 | data.UpdateEntry(RGameStateSection, DebugBotPlayerIDs, "", i); 101 | } 102 | } 103 | 104 | public static void Initialize() 105 | { 106 | ConfigFile defaultGame = new(Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_DEFAULT_GAME_FILENAME)); 107 | IniFile data = defaultGame.data; 108 | 109 | data.UpdateEntry(RGameReplicationInfoSection, GameReplicationInfoKeyPauseTimerInSeconds, DefaultPauseTimerInSeconds); 110 | 111 | defaultGame.Write(); 112 | } 113 | } 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameFiles/Cooked/PawnWeaponUPK.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Model; 2 | 3 | namespace SingleplayerLauncher 4 | { 5 | class PawnWeaponUPK 6 | { 7 | // TODO: make singleton * Load after initialized 8 | 9 | private readonly UPKFile uPKFile; 10 | private readonly Hero hero; 11 | 12 | public PawnWeaponUPK(UPKFile uPKFile, Hero hero) 13 | { 14 | this.uPKFile = uPKFile; 15 | this.hero = hero; 16 | } 17 | 18 | public void SaveChanges() 19 | { 20 | uPKFile.Save(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameFiles/Cooked/SpitfireGameUPK.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Model; 2 | using SingleplayerLauncher.Mods; 3 | using System; 4 | 5 | namespace SingleplayerLauncher 6 | { 7 | class SpitfireGameUPK 8 | { 9 | // TODO: make singleton * Load after initialized 10 | 11 | public static UPKFile SpitfireGameUPKFile; 12 | 13 | private const int MAX_TRAP_TIER = 7; 14 | private const string TRAP_TIER_STRING = "src.TrapStrength"; 15 | 16 | private const int PAR_TIME_CHANGE_INDEX = 0x155A3C2; 17 | private static readonly byte[] UNIQUE_BYTES_REFERENCE_PAR_TIME = { 0x1B, 0x8B, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x1D }; 18 | 19 | public static void ApplyParTime(int parTimeSeconds) 20 | { 21 | byte[] parTimeBytes = BitConverter.GetBytes(parTimeSeconds); 22 | SpitfireGameUPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_PAR_TIME, PAR_TIME_CHANGE_INDEX, 0, parTimeBytes); 23 | } 24 | 25 | public static void ApplyMods(bool areModsEnabled) 26 | { 27 | Mod.UPKFile = SpitfireGameUPKFile; 28 | foreach (Mod mod in Mods.Mods.ModList) 29 | { 30 | if (areModsEnabled && mod.IsEnabled) 31 | { 32 | mod.InstallMod(); 33 | } 34 | else 35 | { 36 | mod.UninstallMod(); 37 | } 38 | } 39 | } 40 | 41 | public static void ApplyMultiplayerPatches(bool isHost) 42 | { 43 | byte gameClientType = isHost ? (byte)2 : (byte)0; 44 | // Fix the weaver upgrade menu not closing when selecting 45 | byte[] uniqueBytesWeaverUpgrade = { 0x07, 0x79, 0x00, 0x9A, 0x38, 0x3A, 0x19, 0x01, 0x07, 0xFA, 0xFF, 0xFF, 0x09, 0x00, 0xE3, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0xE3, 0xFE, 0xFF, 0xFF, 0x38, 0x3A, 0x24 }; 46 | int changeIndexWeaverUpgrade = 0xECCA73; 47 | SpitfireGameUPKFile.ApplyModification(uniqueBytesWeaverUpgrade, changeIndexWeaverUpgrade, 0, gameClientType); 48 | 49 | // Fix the no aggro (also the map overriding the trap cap, initial coins, etc.) 50 | byte[] uniqueBytesNoAggro = { 0x18, 0x27, 0x00, 0x9A, 0x38, 0x3A, 0x19, 0x00, 0xAF, 0xA5, 0x00, 0x00, 0x09, 0x00, 0xE3, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0xE3, 0xFE, 0xFF, 0xFF, 0x38, 0x3A, 0x24 }; 51 | int changeIndexNoAggro = 0x155A4D1; 52 | SpitfireGameUPKFile.ApplyModification(uniqueBytesNoAggro, changeIndexNoAggro, 0, gameClientType); 53 | 54 | // Fix the par time at top for Host 55 | byte[] uniqueBytesParTime = { 0x07, 0x65, 0x00, 0x9A, 0x38, 0x3A, 0x19, 0x01, 0x07, 0xFA, 0xFF, 0xFF, 0x09, 0x00, 0xE3, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0xE3, 0xFE, 0xFF, 0xFF, 0x38, 0x3A, 0x24 }; 56 | int changeIndexParTime = 0x1111436; 57 | SpitfireGameUPKFile.ApplyModification(uniqueBytesParTime, changeIndexParTime, 0, gameClientType); 58 | 59 | // Fix the shown Rift Points for Host 60 | byte[] uniqueBytesRiftPoints = { 0x07, 0xAA, 0x00, 0x9A, 0x38, 0x3A, 0x19, 0x01, 0x07, 0xFA, 0xFF, 0xFF, 0x09, 0x00, 0xE3, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0xE3, 0xFE, 0xFF, 0xFF, 0x38, 0x3A, 0x24 }; 61 | int changeIndexRiftPoints = 0x110C979; 62 | SpitfireGameUPKFile.ApplyModification(uniqueBytesRiftPoints, changeIndexRiftPoints, 0, gameClientType); 63 | 64 | // Fix the score for Host 65 | byte[] uniqueBytesScore = { 0x07, 0x76, 0x01, 0x9A, 0x38, 0x3A, 0x19, 0x01, 0x07, 0xFA, 0xFF, 0xFF, 0x09, 0x00, 0xE3, 0xFE, 0xFF, 0xFF, 0x00, 0x01, 0xE3, 0xFE, 0xFF, 0xFF, 0x38, 0x3A, 0x24 }; 66 | int changeIndexScore = 0x110A5EC; 67 | SpitfireGameUPKFile.ApplyModification(uniqueBytesScore, changeIndexScore, 0, gameClientType); 68 | } 69 | 70 | public static void SaveChanges() 71 | { 72 | SpitfireGameUPKFile.Save(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameFiles/Cooked/UPKHeroObjectContent.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher 2 | { 3 | 4 | public sealed class UPKHeroObjectContent 5 | { 6 | 7 | public UPKFile HeroObjectContent { get; private set; } 8 | 9 | private const int LoadoutSlotByteSize = 4; 10 | private const int LoadoutSlotsNumber = 9; 11 | // Where the actual array of the loadout starts is + 28 bytes from the start of the loadout header. 12 | // Header + Field Type + Field Size in bytes + Array (start?) index + Array number of elements (8 + 8 + 4 + 4 + 4) 13 | private const int LoadoutArraySizeOffset = 16; 14 | private const int LoadoutArrayElementCountOffset = 24; 15 | private const int LoadoutSlotsOffset = 28; 16 | private const int LoadoutSectionLength = LoadoutSlotsOffset + 4 * LoadoutSlotsNumber; 17 | 18 | private const int GuardianSlotsNumber = 2; 19 | // Where the actual array of the guardians starts is + 28 bytes from the start of the guardians header. 20 | // Header + Field type + Field Size in bytes + Array (start?) index + Array number of elements (8 + 8 + 4 + 4 + 4) 21 | private const int GuardiansArraySizeOffset = 16; 22 | private const int GuardiansArrayElementCountOffset = 24; 23 | private const int GuardiansOffset = 28; 24 | 25 | // Header + Field type + Size in bytes + (start?) index (8 + 8 + 4 + 4) 26 | private const int SkinOffsetFromHeader = 24; 27 | 28 | // Header + Field type + Size in bytes + (start?) index (8 + 8 + 4 + 4) 29 | private const int HealthOffsetFromHeader = 24; 30 | private const int HealthMaxOffsetFromHeader = 24; 31 | private const int DefaultArchetypesSectionSize = 32; 32 | 33 | public UPKHeroObjectContent(UPKFile heroObjectContent) 34 | { 35 | HeroObjectContent = heroObjectContent; 36 | } 37 | 38 | private void FillRemovedBytes(int insertIndex) 39 | { 40 | HeroObjectContent.InsertZeroedBytes(insertIndex, 0); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SingleplayerLauncher/GameLauncher.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.GameFiles; 2 | using SingleplayerLauncher.Model; 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Windows.Forms; 7 | 8 | namespace SingleplayerLauncher 9 | { 10 | class GameLauncher 11 | { 12 | private static readonly SpitfireGameUPK SpitfireGameUPK = new(); 13 | 14 | public static void ApplyChanges(bool isHost, bool isSiege = false, int parTimeSeconds = 0, bool isSiegeCoop = false, bool isSiegeAllyBots = false) 15 | { 16 | if (!isSiege) 17 | { 18 | SpitfireGameUPK.SpitfireGameUPKFile = new UPKFile(Path.Combine(Settings.Instance.RootGamePath, FileUtils.UPKS_PATH, FileUtils.SPITFIREGAME_UPK_FILENAME)); 19 | SpitfireGameUPK.ApplyMultiplayerPatches(isHost); 20 | 21 | if (isHost) 22 | { 23 | SpitfireGameUPK.ApplyMods(GameConfig.Instance.ModsEnabled); 24 | SpitfireGameUPK.ApplyParTime(parTimeSeconds); 25 | 26 | // Loadouts are applied as soon as input is received 27 | 28 | GameFiles.DefaultGame.ApplySurvival(); 29 | 30 | GameFiles.CharacterData.ApplyMods(GameConfig.Instance.ModsEnabled); 31 | } 32 | 33 | SpitfireGameUPK.SaveChanges(); 34 | } 35 | else 36 | { 37 | if (isSiegeCoop) 38 | { 39 | GameFiles.DefaultGame.ApplySiegeBots(isSiegeAllyBots); 40 | } 41 | } 42 | 43 | // TODO add multiplayer patches for Siege Host 44 | 45 | GameFiles.DefaultGame.ApplySurvivalSettings(); 46 | } 47 | 48 | public static void FirstLaunchInitialization() 49 | { 50 | string launcherInstallationPath = FileUtils.GetLauncherInstallationPath(Settings.Instance.RootGamePath); 51 | Settings.Instance.LauncherInstallationPath = launcherInstallationPath; 52 | 53 | FileUtils.CreateBackup(FileUtils.INI_CHARACTER_DATA_FILENAME, Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_CHARACTER_DATA_FILENAME)); 54 | FileUtils.CreateBackup(FileUtils.INI_DEFAULT_GAME_FILENAME, Path.Combine(Settings.Instance.RootGamePath, FileUtils.INI_CONFIGS_FOLDER_RELATIVE_PATH, FileUtils.INI_DEFAULT_GAME_FILENAME)); 55 | 56 | GameFiles.CharacterData.Initialize(); 57 | GameFiles.DefaultGame.Initialize(); 58 | 59 | // SpitfireGame (decompress) Initialization 60 | if (!Settings.Instance.IsSiegeInstallation) 61 | { 62 | // TODO extend to decompress Siege UPK 63 | int spitfireGameUPKFileSize = Settings.Instance.IsSiegeInstallation ? FileUtils.SPITFIREGAME_SIEGE_UPK_ORG_SIZE : FileUtils.SPITFIREGAME_UPK_ORG_SIZE; 64 | FileUtils.DecompressUPKFile(FileUtils.SPITFIREGAME_UPK_FILENAME, spitfireGameUPKFileSize); 65 | } 66 | 67 | // Hook (for co-op and non-dedicated server) Initialization 68 | string p2pHookFileName = Settings.Instance.IsSiegeInstallation ? FileUtils.P2P_HOOK_SIEGE_FILENAME : FileUtils.P2P_HOOK_FILENAME; 69 | FileUtils.ApplyHook(p2pHookFileName, FileUtils.P2P_HOOK_TARGET_FILENAME, Path.Combine(Settings.Instance.RootGamePath, FileUtils.SPITFIREGAME_BINARIES_WIN64_PATH)); 70 | FileUtils.ApplyHook(p2pHookFileName, FileUtils.P2P_HOOK_TARGET_FILENAME, Path.Combine(Settings.Instance.RootGamePath, FileUtils.SPITFIREGAME_BINARIES_WIN32_PATH)); 71 | FileUtils.CreateBackup(p2pHookFileName, Path.Combine(Settings.Instance.LauncherInstallationPath, FileUtils.MOD_HOOKS_FOLDER_NAME, p2pHookFileName)); 72 | 73 | // Delete Hooks folder (not needed anymore) 74 | try 75 | { 76 | // TODO: Remove in future release 77 | if (Directory.Exists(Path.Combine(Settings.Instance.RootGamePath, FileUtils.BINARIES_FOLDER_NAME, FileUtils.MOD_HOOKS_FOLDER_NAME))) 78 | { 79 | Directory.Delete(Path.Combine(Settings.Instance.RootGamePath, FileUtils.BINARIES_FOLDER_NAME, FileUtils.MOD_HOOKS_FOLDER_NAME), true); 80 | } 81 | } 82 | catch (Exception e) 83 | { 84 | MessageBox.Show("Error deleting obsolete Hooks folder: " + e.Message); 85 | } 86 | 87 | // Delete UE Extractor folder (not needed here anymore) 88 | try 89 | { 90 | // TODO: Remove in future release 91 | if (Directory.Exists(Path.Combine(Settings.Instance.RootGamePath, FileUtils.BINARIES_FOLDER_NAME, FileUtils.UE_EXTRACTOR_FOLDER_NAME))) 92 | { 93 | Directory.Delete(Path.Combine(Settings.Instance.RootGamePath, FileUtils.BINARIES_FOLDER_NAME, FileUtils.UE_EXTRACTOR_FOLDER_NAME), true); 94 | } 95 | } 96 | catch (Exception e) 97 | { 98 | MessageBox.Show("Error deleting obsolete UE Extractor folder: " + e.Message); 99 | } 100 | 101 | Settings.Instance.FirstRun = false; 102 | Settings.Instance.Save(); 103 | } 104 | 105 | public static void StartGame(string playerName, bool isHost, string hostIP = "", string mapCode = "", int playerCount = 1) 106 | { 107 | Process p = new(); 108 | string filePath = Path.Combine(Settings.Instance.RootGamePath, 109 | Settings.Instance.RunAs32 ? FileUtils.SPITFIREGAME_BINARIES_WIN32_PATH : FileUtils.BINARIES_FOLDER_NAME); 110 | 111 | string exeFullPath = Path.Combine(filePath, FileUtils.SPITFIREGAME_EXE_FILENAME); 112 | 113 | p.StartInfo.FileName = exeFullPath; 114 | p.StartInfo.WorkingDirectory = filePath; 115 | p.StartInfo.Arguments = CreateExeArguments(Settings.Instance.Debug, Settings.Instance.Language, isHost, hostIP, playerName, mapCode, playerCount); 116 | p.StartInfo.UseShellExecute = false; 117 | 118 | p.Start(); 119 | p.WaitForExit(); 120 | } 121 | 122 | private const string EXE_ARGUMENTS = " -seekfreeloadingpcconsole -writepid"; 123 | private const string DEBUG_ARGUMENTS = " -log -ABSLOG=log.txt"; 124 | private const string LANGUAGE_OPTION = " -language="; 125 | 126 | private static string CreateExeArguments(bool debug, string language, bool isHost, string hostIP, string playerName, string mapCode, int playerCount) 127 | { 128 | string arguments = ""; 129 | 130 | string map = mapCode; 131 | string playerGUID = playerName; 132 | string defaultArgs = EXE_ARGUMENTS; 133 | string languageArg = LANGUAGE_OPTION + Language.survivalLanguageMap[language]; 134 | 135 | arguments += isHost ? map : hostIP; 136 | arguments += isHost ? "?listen" : ""; 137 | arguments += isHost ? "?PlayerReadyCount=" + playerCount : ""; 138 | arguments += "?PlayerGUID=" + playerGUID; 139 | arguments += defaultArgs; 140 | arguments += languageArg; 141 | 142 | if (debug) 143 | { 144 | arguments += DEBUG_ARGUMENTS; 145 | } 146 | 147 | arguments += isHost ? " --net_mode=ListenServer" : " --net_mode=Client"; 148 | 149 | return arguments; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/BaseLoadout.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Utils; 2 | using System.Text; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public abstract class BaseLoadout 7 | { 8 | public string Name { get; set; } = ""; 9 | public string PlayerName { get; set; } = "TimeMaster"; 10 | public Hero Hero { get; set; } = Hero.Maximilian; 11 | public Skin Skin { get; set; } = Skin.MaximillianDefault; 12 | public Dye Dye { get; set; } = Dye.Normal; 13 | public SlotItem[] SlotItems { get; set; } = new SlotItem[] { }; 14 | 15 | public Trait[] Traits { get; set; } = new Trait[] { }; 16 | 17 | public const int SLOT_ITEMS_COUNT = 9; 18 | public const int TRAIT_SLOT_COUNT = 4; 19 | public const int TRAIT_BONUS_SLOT_COUNT = 3; 20 | 21 | public const int TRAIT_GREEN_SLOT_IDX = 0; 22 | public const int TRAIT_BLUE_SLOT_IDX = 1; 23 | public const int TRAIT_YELLOW_SLOT_IDX = 2; 24 | public const int TRAIT_NO_BONUS_SLOT_IDX = 3; 25 | 26 | protected const bool FORCE_LENGTH_TWO = true; 27 | 28 | public abstract string Encode(); 29 | public abstract BaseLoadout Decode(string encodedString); 30 | 31 | public bool IsTraitMatchingBonus(int traitIdx) 32 | { 33 | if (traitIdx == TRAIT_NO_BONUS_SLOT_IDX) 34 | { 35 | return false; 36 | } 37 | 38 | Trait trait = Traits[traitIdx]; 39 | 40 | if (traitIdx == TRAIT_BLUE_SLOT_IDX && trait.MatchingSlot == Trait.DIAMOND_BONUS_SLOT || 41 | traitIdx == TRAIT_GREEN_SLOT_IDX && trait.MatchingSlot == Trait.PENTAGON_BONUS_SLOT || 42 | traitIdx == TRAIT_YELLOW_SLOT_IDX && trait.MatchingSlot == Trait.TRIANGLE_BONUS_SLOT) 43 | { 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | protected string EncodeBase() 51 | { 52 | var encodedString = new StringBuilder(); 53 | 54 | // Encode PlayerName 55 | encodedString.Append($"{PlayerName}-"); 56 | 57 | // Encode basic properties 58 | encodedString.Append($"{Base62Converter.Encode(Hero.Id)}{Base62Converter.Encode(Skin.Id, FORCE_LENGTH_TWO)}{Base62Converter.Encode(Dye.Id)}-"); 59 | 60 | return encodedString.ToString(); 61 | } 62 | 63 | protected void DecodeBase(string encodedString) 64 | { 65 | var code = encodedString.Split('-'); 66 | int codeIdx = 0; 67 | 68 | // Decode PlayerName 69 | PlayerName = code[codeIdx++]; 70 | 71 | // Decode basic properties 72 | var basicParts = code[codeIdx++]; 73 | Hero = Hero.GetById(Base62Converter.Decode(basicParts.Substring(0, 1))); 74 | Skin = Skin.GetById(Base62Converter.Decode(basicParts.Substring(1, 2))); // Forced to be 2 chars 75 | Dye = Dye.GetById(Base62Converter.Decode(basicParts.Substring(3, 1))); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/Battleground.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SingleplayerLauncher.Model 4 | { 5 | public class Battleground : IBattleground 6 | { 7 | public string Name { get; private set; } = "CUSTOM"; 8 | 9 | public Map Map { get; set; } 10 | 11 | public GameMode GameMode { get; set; } 12 | 13 | public Difficulty Difficulty { get; set; } 14 | 15 | public int StartingCoin { get; set; } 16 | 17 | public TimeSpan ParTime => throw new NotImplementedException(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/GameInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SingleplayerLauncher.Model 4 | { 5 | public sealed class GameInfo 6 | { 7 | private static readonly GameInfo instance = new(); 8 | 9 | // Explicit static constructor to tell C# compiler 10 | // not to mark type as beforefieldinit /Singleton 11 | static GameInfo() { } 12 | private GameInfo() { } 13 | 14 | public static GameInfo Instance => instance; 15 | 16 | 17 | public static SurvivalLoadout SurvivalLoadout { get; set; } = new SurvivalLoadout(); 18 | public static SiegeLoadout SiegeLoadout { get; set; } = new SiegeLoadout(); 19 | public IBattleground SurvivalBattleground { get; set; } = new Survival(); 20 | public IBattleground SiegeBattleground { get; set; } = new Siege(); 21 | 22 | public int PlayerCount { get; set; } = 1; 23 | public List AllyBotsLoadouts { get; set; } = []; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/Language.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace SingleplayerLauncher.Model 4 | { 5 | public class Language 6 | { 7 | public static readonly Dictionary survivalLanguageMap = new() 8 | { 9 | { "Chinese" , "CHN" }, 10 | { "Deutsch" , "DEU" }, 11 | { "Español" , "ESN" }, 12 | { "Français" , "FRA" }, 13 | { "English" , "INT" }, 14 | { "Italiano" , "ITA" }, 15 | { "Polski" , "POL" }, 16 | { "Português" , "PTB" }, 17 | { "Русский" , "RUS" }, 18 | { "Türkçe" , "TUR" } 19 | }; 20 | 21 | public static readonly Dictionary siegeLanguageMap = new() 22 | { 23 | { "Chinese" , "CHN" }, 24 | { "Deutsch" , "DEU" }, 25 | { "Español" , "ESN" }, 26 | { "Français" , "FRA" }, 27 | { "English" , "INT" }, 28 | { "Italiano" , "ITA" }, 29 | { "Polski" , "POL" }, 30 | { "Türkçe" , "TUR" } 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/LoadoutItem.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Model 2 | { 3 | public abstract class LoadoutItem 4 | { 5 | public int Id { get; protected set; } 6 | public string Name { get; protected set; } 7 | public string SiegeName { get; protected set; } 8 | public string Description { get; protected set; } 9 | public string SiegeDescription { get; protected set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/SiegeLoadout.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Utils; 2 | using System.Text; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public class SiegeLoadout : BaseLoadout 7 | { 8 | public const int WAVES_SLOT_COUNT = 10; 9 | public Wave[] Waves { get; set; } = new Wave[WAVES_SLOT_COUNT]; 10 | public SiegeRole Role { get; set; } = SiegeRole.Defender; 11 | public int BotTrapLevel { get; set; } 12 | 13 | public SiegeLoadout() 14 | { 15 | SlotItems = [Gear.MendingRoot, Gear.MagesClover, Trap.Barricade, Trap.ViscousTar, Trap.FlipTrap, Trap.WallBlades, Trap.ArrowWall, Trap.ConcussivePounder, Trap.CeilingBallista]; 16 | Traits = new SiegeTrait[TRAIT_SLOT_COUNT]; 17 | } 18 | 19 | 20 | public override string Encode() 21 | { 22 | var encodedString = new StringBuilder(EncodeBase()); 23 | 24 | // Encode Role 25 | encodedString.Append(Role != null ? Base62Converter.Encode(Role.Id) : "0"); 26 | encodedString.Append('-'); 27 | 28 | // Encode SlotItems 29 | foreach (var slotItem in SlotItems) 30 | { 31 | encodedString.Append(slotItem != null ? Base62Converter.Encode(slotItem.Id, FORCE_LENGTH_TWO) : "00"); 32 | } 33 | encodedString.Append('-'); 34 | 35 | // Encode Waves 36 | for (int i = 0; i < WAVES_SLOT_COUNT; i++) 37 | { 38 | Wave wave = Waves[i]; 39 | encodedString.Append(wave != null ? Base62Converter.Encode(wave.Id) : "0"); 40 | } 41 | encodedString.Append('-'); 42 | 43 | // Encode Traits 44 | foreach (var trait in Traits) 45 | { 46 | encodedString.Append(trait != null ? Base62Converter.Encode(trait.Id) : "0"); 47 | } 48 | 49 | return encodedString.ToString(); 50 | } 51 | 52 | public override BaseLoadout Decode(string encodedString) 53 | { 54 | DecodeBase(encodedString); 55 | var code = encodedString.Split('-'); 56 | int codeIdx = 2; 57 | 58 | // Decode Role 59 | var role = code[codeIdx++]; 60 | Role = SiegeRole.GetById(Base62Converter.Decode(role)); 61 | 62 | // Decode SlotItems 63 | var slotItemParts = code[codeIdx++]; 64 | SlotItems = new SlotItem[SLOT_ITEMS_COUNT]; 65 | for (int i = 0; i < SLOT_ITEMS_COUNT; i++) 66 | { 67 | var slotItem = slotItemParts.Substring(i * 2, 2); 68 | SlotItems[i] = slotItem != "00" ? SlotItem.GetById(Base62Converter.Decode(slotItem)) : null; // Forced to be 2 chars 69 | } 70 | 71 | // Decode Waves 72 | var waveParts = code[codeIdx++]; 73 | Waves = new Wave[WAVES_SLOT_COUNT]; 74 | for (int i = 0; i < WAVES_SLOT_COUNT; i++) 75 | { 76 | var wave = waveParts.Substring(i, 1); 77 | Waves[i] = wave != "0" ? Wave.GetById(Base62Converter.Decode(wave)) : null; 78 | } 79 | 80 | // Decode Traits 81 | var traitParts = code[codeIdx++]; 82 | Traits = new SiegeTrait[TRAIT_SLOT_COUNT]; 83 | for (int i = 0; i < TRAIT_SLOT_COUNT; i++) 84 | { 85 | var trait = traitParts.Substring(i, 1); 86 | Traits[i] = trait != "0" ? SiegeTrait.GetById(Base62Converter.Decode(trait)) : null; 87 | } 88 | 89 | return this; 90 | } 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/SlotItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SingleplayerLauncher.Model 5 | { 6 | public abstract class SlotItem : LoadoutItem 7 | { 8 | public string ItemTemplateName { get; protected set; } 9 | 10 | public static Dictionary SlotItemsById = []; 11 | 12 | public static SlotItem GetById(int id) 13 | { 14 | SlotItemsById = Trap.TrapsById 15 | .Concat(Gear.GearsById) 16 | .ToDictionary(pair => pair.Key, pair => pair.Value); 17 | 18 | if (SlotItemsById.TryGetValue(id, out var slotItem)) 19 | { 20 | return slotItem; 21 | } 22 | return new Trap { Id = 0, ItemTemplateName = "Unknown", Description = "Unknown" }; 23 | } 24 | 25 | public static Dictionary GetAllSlotItems() 26 | { 27 | return new List>() { Gear.Gears, Trap.Traps } 28 | .SelectMany(dict => dict) 29 | .ToDictionary(pair => pair.Key, pair => pair.Value); 30 | } 31 | 32 | public static Dictionary GetAllSiegeSlotItems() 33 | { 34 | return new List>() { Gear.Gears, Trap.SiegeTraps } 35 | .SelectMany(dict => dict) 36 | .ToDictionary(pair => pair.Key, pair => pair.Value); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Model/SurvivalLoadout.cs: -------------------------------------------------------------------------------- 1 | using SingleplayerLauncher.Utils; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace SingleplayerLauncher.Model 6 | { 7 | public class SurvivalLoadout : BaseLoadout 8 | { 9 | public const int GUARDIAN_SLOT_COUNT = 2; 10 | public const int CONSUMABLE_SLOT_COUNT = 2; 11 | public const int TRAP_PART_SLOT_COUNT = 3; 12 | 13 | public SurvivalLoadout() 14 | { 15 | SlotItems = new SlotItem[SLOT_ITEMS_COUNT] { Gear.MendingRoot, Gear.MagesClover, Trap.Barricade, Trap.ViscousTar, Trap.FlipTrap, Trap.WallBlades, Trap.ArrowWall, Trap.ConcussivePounder, Trap.CeilingBallista 16 | }; 17 | } 18 | 19 | public Guardian[] Guardians { get; set; } = new Guardian[GUARDIAN_SLOT_COUNT] { Guardian.DragonGuardian, Guardian.MoonGuardian }; 20 | 21 | public Consumable[] Consumables { get; set; } = new Consumable[CONSUMABLE_SLOT_COUNT] { Consumable.LuckPotion, Consumable.UnchainedScroll }; 22 | 23 | public TrapPart[,] TrapParts { get; set; } = new TrapPart[SLOT_ITEMS_COUNT, TRAP_PART_SLOT_COUNT]; 24 | 25 | public TrapPart[] GetTrapPartsForLoadout(int loadoutSlotIdx) 26 | { 27 | return Enumerable.Range(0, TRAP_PART_SLOT_COUNT) 28 | .Select(c => TrapParts[loadoutSlotIdx, c]) 29 | .ToArray(); 30 | } 31 | 32 | public override string Encode() 33 | { 34 | var encodedString = new StringBuilder(EncodeBase()); 35 | 36 | // Encode SlotItems 37 | foreach (var slotItem in SlotItems) 38 | { 39 | encodedString.Append(slotItem != null ? Base62Converter.Encode(slotItem.Id, FORCE_LENGTH_TWO) : "00"); 40 | } 41 | encodedString.Append("-"); 42 | 43 | // Encode Guardians 44 | foreach (var guardian in Guardians) 45 | { 46 | encodedString.Append(guardian != null ? Base62Converter.Encode(guardian.Id) : "0"); 47 | } 48 | encodedString.Append("-"); 49 | 50 | // Encode Consumables 51 | foreach (var consumable in Consumables) 52 | { 53 | encodedString.Append(consumable != null ? Base62Converter.Encode(consumable.Id) : "0"); 54 | } 55 | encodedString.Append("-"); 56 | 57 | // Encode Traits 58 | foreach (var trait in Traits) 59 | { 60 | encodedString.Append(trait != null ? Base62Converter.Encode(trait.Id) : "0"); 61 | } 62 | encodedString.Append("-"); 63 | 64 | // Encode TrapParts 65 | for (int i = 0; i < SLOT_ITEMS_COUNT; i++) 66 | { 67 | for (int j = 0; j < TRAP_PART_SLOT_COUNT; j++) 68 | { 69 | var trapPart = TrapParts[i, j]; 70 | encodedString.Append(trapPart != null ? Base62Converter.Encode(trapPart.Id) : "0"); 71 | } 72 | } 73 | 74 | return encodedString.ToString(); 75 | } 76 | 77 | public override BaseLoadout Decode(string encodedString) 78 | { 79 | DecodeBase(encodedString); 80 | var code = encodedString.Split('-'); 81 | int codeIdx = 2; 82 | 83 | // Decode SlotItems 84 | var slotItemParts = code[codeIdx++]; 85 | SlotItems = new SlotItem[SLOT_ITEMS_COUNT]; 86 | for (int i = 0; i < SLOT_ITEMS_COUNT; i++) 87 | { 88 | var slotItem = slotItemParts.Substring(i * 2, 2); 89 | SlotItems[i] = slotItem != "00" ? SlotItem.GetById(Base62Converter.Decode(slotItem)) : null; // Forced to be 2 chars 90 | } 91 | 92 | // Decode Guardians 93 | var guardianParts = code[codeIdx++]; 94 | Guardians = new Guardian[GUARDIAN_SLOT_COUNT]; 95 | for (int i = 0; i < GUARDIAN_SLOT_COUNT; i++) 96 | { 97 | var guardian = guardianParts.Substring(i, 1); 98 | Guardians[i] = guardian != "0" ? Guardian.GetById(Base62Converter.Decode(guardian)) : null; 99 | } 100 | 101 | // Decode Consumables 102 | var consumableParts = code[codeIdx++]; 103 | Consumables = new Consumable[CONSUMABLE_SLOT_COUNT]; 104 | for (int i = 0; i < CONSUMABLE_SLOT_COUNT; i++) 105 | { 106 | var consumable = consumableParts.Substring(i, 1); 107 | Consumables[i] = consumable != "0" ? Consumable.GetById(Base62Converter.Decode(consumable)) : null; 108 | } 109 | 110 | // Decode Traits 111 | var traitParts = code[codeIdx++]; 112 | Traits = new SurvivalTrait[TRAIT_SLOT_COUNT]; 113 | for (int i = 0; i < TRAIT_SLOT_COUNT; i++) 114 | { 115 | var trait = traitParts.Substring(i, 1); 116 | Traits[i] = trait != "0" ? SurvivalTrait.GetById(Base62Converter.Decode(trait)) : null; 117 | } 118 | 119 | // Decode TrapParts 120 | var trapPartParts = code[codeIdx]; 121 | TrapParts = new TrapPart[SLOT_ITEMS_COUNT, TRAP_PART_SLOT_COUNT]; 122 | int trapPartIndex = 0; 123 | for (int i = 0; i < SLOT_ITEMS_COUNT; i++) 124 | { 125 | for (int j = 0; j < TRAP_PART_SLOT_COUNT; j++) 126 | { 127 | var trapPartChar = trapPartParts.Substring(trapPartIndex++, 1); 128 | TrapParts[i, j] = trapPartChar != "0" ? TrapPart.GetById(Base62Converter.Decode(trapPartChar)) : null; 129 | } 130 | } 131 | 132 | return this; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/AccountLevelOverride.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class AccountLevelOverride : Mod 4 | { 5 | public override bool InstallMod() 6 | { 7 | return true; 8 | } 9 | 10 | public override bool UninstallMod() 11 | { 12 | return true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/AdditionalheroWeapon.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class AdditionalHeroWeapon : Mod 4 | { 5 | public new string Value { get; set; } 6 | 7 | public override bool InstallMod() 8 | { 9 | return true; 10 | } 11 | 12 | public override bool UninstallMod() 13 | { 14 | return true; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/EnhancedTrapRotation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SingleplayerLauncher.Mods 4 | { 5 | public class EnhancedTrapRotation : Mod 6 | { 7 | readonly byte[] DEGREES_90_IN_BYTES = BitConverter.GetBytes(90); 8 | readonly byte[] DEGREES_10_IN_BYTES = BitConverter.GetBytes(10); // Actually seems to work as 22.5 9 | 10 | private const int CHANGE_INDEX = 0x1544729; 11 | 12 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x00, 0x00, 0x00, 0xD6, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC8, 0x42, 0x58, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 13 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 0; 14 | 15 | public override bool InstallMod() 16 | { 17 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, DEGREES_10_IN_BYTES); 18 | return true; 19 | } 20 | 21 | public override bool UninstallMod() 22 | { 23 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, DEGREES_90_IN_BYTES); 24 | return true; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/GodMode.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class GodMode : Mod 4 | { 5 | public override bool InstallMod() 6 | { 7 | return true; 8 | } 9 | 10 | public override bool UninstallMod() 11 | { 12 | return true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/Hardcore.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class Hardcore : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x12, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 0; 7 | 8 | private const int CHANGE_INDEX = 0x1102F88; 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 19 | return true; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/InvincibleBarricades.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class InvincibleBarricades : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x00, 0x00, 0x00, 0x6E, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xCB, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 0; 7 | 8 | private const int CHANGE_INDEX = 0x184631A; // ( CB 15 00 00 ): CanBeDamaged 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 19 | return true; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/Mod.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public abstract class Mod 4 | { 5 | public string Name { get; private set; } 6 | 7 | internal static UPKFile UPKFile { get; set; } 8 | 9 | public bool IsEnabled { get; set; } = false; 10 | 11 | public int Value { get; set; } = 0; 12 | 13 | public abstract bool InstallMod(); 14 | public abstract bool UninstallMod(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/Mods.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using SingleplayerLauncher.Names; 3 | 4 | namespace SingleplayerLauncher.Mods 5 | { 6 | static class Mods 7 | { 8 | // static members 9 | public static Mod NoTrapCap = new NoTrapCap(); 10 | public static Mod NoLimitUniqueTraps = new NoLimitUniqueTraps(); 11 | public static Mod InvincibleBarricades = new InvincibleBarricades(); 12 | public static Mod TrapsAnySurface = new TrapsAnySurface(); 13 | public static Mod TrapsInTraps = new TrapsInTraps(); 14 | public static Mod NoTrapGrid = new NoTrapGrid(); 15 | public static Mod GodMode = new GodMode(); 16 | public static Mod ShowTrapDamageFlyoffs = new ShowTrapDamageFlyoffs(); 17 | public static Mod Hardcore = new Hardcore(); 18 | public static Mod EnhancedTrapRotation = new EnhancedTrapRotation(); 19 | public static Mod SellTrapsAnytime = new SellTrapsAnytime(); 20 | public static Mod StartingCoinOverride = new StartingCoinOverride(); 21 | public static Mod AccountLevelOverride = new AccountLevelOverride(); 22 | public static Mod TrapTierOverride = new TrapTierOverride(); 23 | public static AdditionalHeroWeapon AdditionalHeroWeapon = new(); 24 | 25 | public readonly static List ModList = 26 | [ 27 | NoTrapCap, 28 | NoLimitUniqueTraps, 29 | InvincibleBarricades, 30 | TrapsAnySurface, 31 | TrapsInTraps, 32 | NoTrapGrid, 33 | GodMode, 34 | ShowTrapDamageFlyoffs, 35 | Hardcore, 36 | EnhancedTrapRotation, 37 | SellTrapsAnytime, 38 | StartingCoinOverride, 39 | AccountLevelOverride, 40 | TrapTierOverride, 41 | AdditionalHeroWeapon 42 | ]; 43 | 44 | public readonly static Dictionary ModsByName = new() 45 | { 46 | { Names.Mod.NO_TRAP_CAP, NoTrapCap }, 47 | { Names.Mod.NO_LIMIT_UNIQUE_TRAPS, NoLimitUniqueTraps }, 48 | { Names.Mod.INVINCIBLE_BARRICADES, InvincibleBarricades }, 49 | { Names.Mod.TRAPS_ANY_SURFACE, TrapsAnySurface }, 50 | { Names.Mod.TRAPS_IN_TRAPS, TrapsInTraps }, 51 | { Names.Mod.NO_TRAP_GRID, NoTrapGrid }, 52 | { Names.Mod.GOD_MODE, GodMode }, 53 | { Names.Mod.SHOW_TRAP_DAMAGE_FLYOFFS, ShowTrapDamageFlyoffs }, 54 | { Names.Mod.HARDCORE, Hardcore }, 55 | { Names.Mod.ENHANCED_TRAP_ROTATION, EnhancedTrapRotation }, 56 | { Names.Mod.SELL_TRAPS_ANYTIME, SellTrapsAnytime }, 57 | { Names.Mod.STARTING_COIN_OVERRIDE, StartingCoinOverride }, 58 | { Names.Mod.ACCOUNT_LEVEL_OVERRIDE, AccountLevelOverride }, 59 | { Names.Mod.TRAP_TIER_OVERRIDE, TrapTierOverride }, 60 | { Names.Mod.ADDITIONAL_HERO_WEAPON, AdditionalHeroWeapon } 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/NoLimitUniqueTraps.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class NoLimitUniqueTraps : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x37, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0xC4, 0x00, 0x00, 0x37, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0xC4, 0x00, 0x00, 0x5F, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 0; 7 | 8 | private const int CHANGE_INDEX = 0x16157FE; // ( 5F B0 00 00 ): UseCountLimiter 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 19 | return true; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/NoTrapCap.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class NoTrapCap : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x00, 0x00, 0x6B, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xEE, 0x00, 0x00, 0x35, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 74; 7 | 8 | private const int CHANGE_INDEX = 0x15444A3; 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 19 | return true; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/NoTrapGrid.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class NoTrapGrid : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x00, 0x00, 0x6B, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xEE, 0x00, 0x00, 0x35, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 49; 7 | 8 | private const int CHANGE_INDEX = 0x154448A; 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 19 | return true; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/SellTrapsAnytime.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class SellTrapsAnytime : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x04, 0x27, 0x07, 0x33, 0x02, 0x9A, 0x38, 0x3A, 0x1A, 0x38, 0x3A, 0x1B, 0x79, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x19, 0x00, 0xED, 0x34, 0x00, 0x00, 0x09, 0x00, 0xB2, 0x9D, 0x00, 0x00, 0x00, 0x01, 0xB2, 0x9D, 0x00, 0x00, 0x38, 0x3A, 0x24, 0x01, 0x16, 0x04, 0x27, 0x04 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 0; 7 | 8 | private const int CHANGE_INDEX = 0x0F172B8; 9 | public override bool InstallMod() 10 | { 11 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 39); 12 | return true; 13 | } 14 | 15 | public override bool UninstallMod() 16 | { 17 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 40); 18 | return true; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/ShowTrapDamageFlyoffs.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class ShowTrapDamageFlyoffs : Mod 4 | { 5 | public override bool InstallMod() 6 | { 7 | return true; 8 | } 9 | 10 | public override bool UninstallMod() 11 | { 12 | return true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/StartingCoinOverride.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class StartingCoinOverride : Mod 4 | { 5 | public override bool InstallMod() 6 | { 7 | return true; 8 | } 9 | 10 | public override bool UninstallMod() 11 | { 12 | return true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/TrapTierOverride.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class TrapTierOverride : Mod 4 | { 5 | public override bool InstallMod() 6 | { 7 | return true; 8 | } 9 | 10 | public override bool UninstallMod() 11 | { 12 | return true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/TrapsAnySurface.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class TrapsAnySurface : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE_1 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xDF, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 6 | readonly private byte[] UNIQUE_BYTES_REFERENCE_2 = { 0x00, 0x00, 0x00, 0x6B, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xEE, 0x00, 0x00, 0x35, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 7 | readonly private byte[] UNIQUE_BYTES_REFERENCE_3 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; 8 | 9 | readonly private int OFFSET_FROM_UNIQUE_BYTES_1 = 0; 10 | readonly private int OFFSET_FROM_UNIQUE_BYTES_2 = 0; 11 | readonly private int OFFSET_FROM_UNIQUE_BYTES_3 = 0; 12 | 13 | 14 | private const int CHANGE_INDEX_1 = 0x154453A; 15 | private const int CHANGE_INDEX_2 = 0x1544459; 16 | private const int CHANGE_INDEX_3 = 0x1544D37; 17 | 18 | public override bool InstallMod() 19 | { 20 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_1, CHANGE_INDEX_1, OFFSET_FROM_UNIQUE_BYTES_1, new byte[] { 0xF1, 0x6F }); 21 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_2, CHANGE_INDEX_2, OFFSET_FROM_UNIQUE_BYTES_2, new byte[] { 0xFA, 0x6F }); 22 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_3, CHANGE_INDEX_3, OFFSET_FROM_UNIQUE_BYTES_3, new byte[] { 0x16, 0x70 }); 23 | 24 | return true; 25 | } 26 | 27 | public override bool UninstallMod() 28 | { 29 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_1, CHANGE_INDEX_1, OFFSET_FROM_UNIQUE_BYTES_1, new byte[] { 0x32, 0x65 }); 30 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_2, CHANGE_INDEX_2, OFFSET_FROM_UNIQUE_BYTES_2, new byte[] { 0x3B, 0x57 }); 31 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE_3, CHANGE_INDEX_3, OFFSET_FROM_UNIQUE_BYTES_3, new byte[] { 0xE4, 0x0F }); 32 | 33 | return true; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Mods/TrapsInTraps.cs: -------------------------------------------------------------------------------- 1 | namespace SingleplayerLauncher.Mods 2 | { 3 | public class TrapsInTraps : Mod 4 | { 5 | readonly private byte[] UNIQUE_BYTES_REFERENCE = { 0x00, 0x00, 0x00, 0x6B, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1D, 0xEE, 0x00, 0x00, 0x35, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }; 6 | readonly private int OFFSET_FROM_UNIQUE_BYTES = 99; 7 | 8 | private const int CHANGE_INDEX = 0x15444BC; 9 | 10 | public override bool InstallMod() 11 | { 12 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 0); 13 | return true; 14 | } 15 | 16 | public override bool UninstallMod() 17 | { 18 | UPKFile.ApplyModification(UNIQUE_BYTES_REFERENCE, CHANGE_INDEX, OFFSET_FROM_UNIQUE_BYTES, 1); 19 | return true; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /SingleplayerLauncher/ProjectRechained.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.10.35013.160 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProjectRechainedLauncher", "ProjectRechainedLauncher.csproj", "{5902F0C2-713E-4BF0-8037-5094AF55CEEB}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {5902F0C2-713E-4BF0-8037-5094AF55CEEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {5902F0C2-713E-4BF0-8037-5094AF55CEEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {5902F0C2-713E-4BF0-8037-5094AF55CEEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {5902F0C2-713E-4BF0-8037-5094AF55CEEB}.Release|Any CPU.Build.0 = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {CDA9A273-8962-4D45-865D-05A895A7E77D} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /SingleplayerLauncher/ProjectRechainedLauncher.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0-windows 4 | WinExe 5 | ProjectRechained 6 | false 7 | publish\ 8 | true 9 | Disk 10 | false 11 | Foreground 12 | 7 13 | Days 14 | false 15 | false 16 | true 17 | 0 18 | 1.0.0.%2a 19 | false 20 | true 21 | false 22 | true 23 | true 24 | omdu-U1_512.png 25 | omdu-U1_512.ico 26 | 27 | 28 | true 29 | bin\Debug Siege\ 30 | 31 | 32 | 33 | False 34 | .NET Framework 3.5 SP1 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | True 49 | True 50 | Settings.settings 51 | 52 | 53 | 54 | 55 | SettingsSingleFileGenerator 56 | Settings.Designer.cs 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ProjectRechained.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProjectRechained.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 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 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace ProjectRechained.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.10.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | [global::System.Configuration.DefaultSettingValueAttribute("True")] 29 | public bool FirstRun { 30 | get { 31 | return ((bool)(this["FirstRun"])); 32 | } 33 | set { 34 | this["FirstRun"] = value; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | True 7 | 8 | 9 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/Base62Converter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SingleplayerLauncher.Utils 4 | { 5 | /// 6 | /// Provides methods for encoding integers to Base62 strings and decoding Base62 strings to integers. 7 | /// 8 | public static class Base62Converter 9 | { 10 | /// 11 | /// The character set used for Base62 encoding, consisting of digits, uppercase letters, and lowercase letters. 12 | /// 13 | private const string Base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 14 | 15 | /// 16 | /// Encodes an integer value to a Base62 string. 17 | /// 18 | /// The integer value to encode. Must be between 0 and 3843, inclusive. 19 | /// If true, always returns a two-character string. If false, returns a one-character string for values less than 62. 20 | /// A Base62 encoded string representation of the input value. 21 | /// Thrown when the input value is less than 0 or greater than or equal to 3844. 22 | public static string Encode(int value, bool forceLengthTwo = false) 23 | { 24 | if (value < 0 || value >= 3844) // 62 * 62 = 3844 25 | { 26 | throw new ArgumentOutOfRangeException(nameof(value), "Value must be in the range 0-3843."); 27 | } 28 | 29 | if (value < 62 && !forceLengthTwo) 30 | { 31 | return Base62Chars[value].ToString(); 32 | } 33 | 34 | int high = value / 62; 35 | int low = value % 62; 36 | return $"{Base62Chars[high]}{Base62Chars[low]}"; 37 | } 38 | 39 | /// 40 | /// Decodes a Base62 encoded string to its integer value. 41 | /// 42 | /// The Base62 encoded string to decode. Must be either one or two characters long. 43 | /// The integer value represented by the Base62 encoded string. 44 | /// Thrown when the input string length is not 1 or 2. 45 | public static int Decode(string str) 46 | { 47 | if (str.Length == 1) 48 | { 49 | return Base62Chars.IndexOf(str[0]); 50 | } 51 | else if (str.Length == 2) 52 | { 53 | int high = Base62Chars.IndexOf(str[0]); 54 | int low = Base62Chars.IndexOf(str[1]); 55 | return high * 62 + low; 56 | } 57 | else 58 | { 59 | throw new ArgumentException("Invalid Base62 encoded string length.", nameof(str)); 60 | } 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/Comment.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SingleplayerLauncher.Utils 3 | { 4 | public class Comment : IniElement 5 | { 6 | public Comment(string rawText) : base(rawText) 7 | { 8 | } 9 | } 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/Entry.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SingleplayerLauncher.Utils 3 | { 4 | public class Entry : IniElement 5 | { 6 | public string Key { get; private set; } 7 | public string Value { get; private set; } 8 | 9 | public Entry(string line) : base(line) 10 | { 11 | var index = line.IndexOf('='); 12 | if (index >= 0) 13 | { 14 | Key = line.Substring(0, index).Trim(); 15 | Value = line.Substring(index + 1).Trim(); 16 | } 17 | else 18 | { 19 | Key = line.Trim(); 20 | Value = string.Empty; 21 | } 22 | } 23 | 24 | public void UpdateValue(string newValue) 25 | { 26 | Value = newValue; 27 | RawText = $"{Key}={Value}"; 28 | } 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/IniElement.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SingleplayerLauncher.Utils 3 | { 4 | public abstract class IniElement 5 | { 6 | public string RawText { get; set; } 7 | 8 | protected IniElement(string rawText) 9 | { 10 | RawText = rawText; 11 | } 12 | 13 | public override string ToString() 14 | { 15 | return RawText; 16 | } 17 | } 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/IniFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace SingleplayerLauncher.Utils 7 | { 8 | 9 | public class IniFile 10 | { 11 | public Dictionary Sections { get; private set; } 12 | 13 | public IniFile() 14 | { 15 | Sections = []; 16 | } 17 | 18 | public void Load(string filePath) 19 | { 20 | var lines = File.ReadAllLines(filePath); 21 | Section currentSection = null; 22 | 23 | foreach (var line in lines) 24 | { 25 | var trimmedLine = line.Trim(); 26 | if (trimmedLine.Length == 0) 27 | { 28 | continue; 29 | } 30 | 31 | if (trimmedLine.StartsWith(";")) 32 | { 33 | // Comment line 34 | var comment = new Comment(line); 35 | if (currentSection != null) 36 | { 37 | currentSection.AddElement(comment); 38 | } 39 | else 40 | { 41 | // Add comment outside of any section 42 | var sectionKey = Guid.NewGuid().ToString(); 43 | Sections[sectionKey] = new Section(null, line); 44 | Sections[sectionKey].AddElement(comment); 45 | } 46 | } 47 | else if (trimmedLine.StartsWith("[") && trimmedLine.EndsWith("]")) 48 | { 49 | // New section 50 | var sectionName = trimmedLine.Substring(1, trimmedLine.Length - 2); 51 | currentSection = new Section(sectionName, line); 52 | Sections[sectionName] = currentSection; 53 | } 54 | else 55 | { 56 | // Entry in section 57 | var entry = new Entry(line); 58 | if (currentSection != null) 59 | { 60 | currentSection.AddElement(entry); 61 | } 62 | else 63 | { 64 | // Add entry outside of any section 65 | var sectionKey = Guid.NewGuid().ToString(); 66 | Sections[sectionKey] = new Section(null, line); 67 | Sections[sectionKey].AddElement(entry); 68 | } 69 | } 70 | } 71 | } 72 | 73 | public void Save(string filePath) 74 | { 75 | var sb = new StringBuilder(); 76 | 77 | foreach (var section in Sections.Values) 78 | { 79 | if (section.Name != null) 80 | { 81 | sb.AppendLine($"[{section.Name}]"); 82 | } 83 | 84 | foreach (var element in section.Elements) 85 | { 86 | sb.AppendLine(element.ToString()); 87 | } 88 | 89 | sb.AppendLine(); // Add an extra line between sections for readability 90 | } 91 | 92 | File.WriteAllText(filePath, sb.ToString()); 93 | } 94 | 95 | public List FindEntries(string sectionName, string key) 96 | { 97 | if (Sections.TryGetValue(sectionName, out var section)) 98 | { 99 | return section.FindEntries(key); 100 | } 101 | return null; 102 | } 103 | 104 | public void UpdateEntry(string sectionName, string key, string newValue, int index = 0) 105 | { 106 | if (!Sections.TryGetValue(sectionName, out var section)) 107 | { 108 | section = new Section(sectionName, $"[{sectionName}]"); 109 | Sections[sectionName] = section; 110 | } 111 | 112 | var entries = section.FindEntries(key); 113 | if (entries != null && index < entries.Count) 114 | { 115 | entries[index].UpdateValue(newValue); 116 | } 117 | else 118 | { 119 | var newEntry = new Entry($"{key}={newValue}"); 120 | section.AddElement(newEntry); 121 | } 122 | } 123 | 124 | public void RemoveSection(string sectionName) 125 | { 126 | if (!Sections.TryGetValue(sectionName, out var section)) 127 | { 128 | // Section not found; nothing to remove 129 | return; 130 | } 131 | 132 | Sections.Remove(sectionName); 133 | } 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /SingleplayerLauncher/Utils/Section.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace SingleplayerLauncher.Utils 3 | { 4 | using System.Collections.Generic; 5 | 6 | public class Section : IniElement 7 | { 8 | public string Name { get; private set; } 9 | public List Elements { get; private set; } 10 | private readonly Dictionary> entries; 11 | 12 | public Section(string name, string rawText) : base(rawText) 13 | { 14 | Name = name; 15 | Elements = []; 16 | entries = []; 17 | } 18 | 19 | public void AddElement(IniElement element) 20 | { 21 | Elements.Add(element); 22 | if (element is Entry entry) 23 | { 24 | if (!entries.TryGetValue(entry.Key, out var entryList)) 25 | { 26 | entryList = []; 27 | entries[entry.Key] = entryList; 28 | } 29 | entryList.Add(entry); 30 | } 31 | } 32 | 33 | public List FindEntries(string key) 34 | { 35 | if (entries.TryGetValue(key, out var entryList)) 36 | { 37 | return entryList; 38 | } 39 | return []; 40 | } 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /SingleplayerLauncher/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | True 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /SingleplayerLauncher/omdu-U1_512.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimeMaster18/Project-Rechained/07e60a4a377128522a41f00606ac2942bc3addde/SingleplayerLauncher/omdu-U1_512.ico --------------------------------------------------------------------------------