diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml index bb592bce..941776f3 100644 --- a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -32,42 +32,15 @@ body: - Always validations: required: true - - type: textarea - id: operating-system - attributes: - label: What operating system are you using? - description: Operating system - placeholder: Tell us what operating system you using (Windows 11, Windows 10, etc.) - value: "Windows 10 21H2" - validations: - required: true - type: textarea id: version attributes: label: What version of the program are you using? description: Program Version - placeholder: Tell us what version you using (x.y.z) + placeholder: Tell us what version you are using (x.y.z) value: "Latest version" validations: required: true - - type: dropdown - id: game-version - attributes: - label: What version of the game are you using? - multiple: true - options: - - High-Grade Edition - - Non High-Grade Edition - validations: - required: true - - type: input - id: quest-id - attributes: - label: Quest ID - description: What is the Quest ID where the issue occurs? Enter the quest ID that you see in the overlay, enabled by going into Config -> Quest Log -> Settings. If the issue occurs in multiple quests, enter an example quest ID and tell us in the issue description that it happens in multiple quests. If the issue occurs outside quests, enter 0. - placeholder: '0' - validations: - required: true - type: markdown attributes: value: | diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 243c0db9..13ae7ea0 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -28,4 +28,6 @@ jobs: - name: Build run: dotnet build --configuration Release --no-restore - name: Test - run: dotnet test --no-restore --verbosity normal \ No newline at end of file + run: | + cd MHFZ_OverlayTest + dotnet test --no-restore --verbosity normal \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e7d3c366..be4c638b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,200 @@ +## [0.30.0](https://github.com/DorielRivalet/MHFZ_Overlay/compare/v0.29.1...v0.30.0) (2023-09-01) + + +### Features + +* **achievements:** add more achievements ([644ff63](https://github.com/DorielRivalet/MHFZ_Overlay/commit/644ff63e2b0ee10ba3ae30b48063c4a5595b6205)) +* **achievements:** add more achievements ([2f1d405](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2f1d4057cb6cc2127742330729079896218582b9)) +* **achievements:** add more achievements ([5d17dc4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/5d17dc48ce2a3063b9aebdaaf7f92bc7cabf725e)) +* **achievements:** expand achievements collection ([8ee9db8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8ee9db8c0e04238e7fc2452d269e430d65b79f69)) +* **achievements:** expand collection ([9ab8271](https://github.com/DorielRivalet/MHFZ_Overlay/commit/9ab82717d70ee20896df00e6fabcb31f6aded597)) +* **achievements:** expand collection ([0640049](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0640049bc5239005de914042453fcdd401d3dec3)) +* **achievements:** expand collection ([7c083ab](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7c083ab3ea7ffcf180733cb91fda0eae5a05bf78)) +* **achievements:** expand collection ([2c05144](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2c05144bc5d45eabdeed670916183d7239f62a86)) +* **achievements:** make more obtainable achievements ([8fa3be8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8fa3be817e178e4f1ede307f96e758cea753a78b)) +* **achievements:** update achievements ([0e46864](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0e4686476f3f2d762986186f4252715d449487aa)) +* **achievements:** update achievements explanation in configuration window ([e0e1caa](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e0e1caa8e3f06b18a40dcd64184cd1cf39b5be58)) +* **achievements:** update achievements explanation in configuration window ([d1a3b3e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/d1a3b3e5a16625dba59caf41bf0bcd503251334d)) +* add ancient dragon images ([5cee57e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/5cee57e2a1e77bb76000113522509420d091d6dd)) +* add automatic git stats scripts ([c4d3d71](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c4d3d718c8166f57ff4bc73bcb3b87d5b3e9a7c1)) +* add challenge button sound effects ([c926b7a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c926b7a352384ef273b3380f5ce0767e18a9d34e)) +* add frontier data collections for colors ([e014250](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e0142509547373a8dfa005c50411bfb48fd51ed1)) +* add hc/ul options ([ff34d9c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ff34d9c29be2c619fc9e7bdc019b17cbc5f8f66e)) +* add messengers ([49c0397](https://github.com/DorielRivalet/MHFZ_Overlay/commit/49c039795079e036eb7e645a4ce0cdedd15151f8)) +* add more achievements ([0c45548](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0c45548a3de7eaebe9ac92c6c1212178e5bea4ce)) +* add more bitfields ([f4c428a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f4c428a6d7d463c3939a30408c979d6d0a5778bc)) +* add quest id logging ([9eff2a6](https://github.com/DorielRivalet/MHFZ_Overlay/commit/9eff2a627ba29da8cae865df95dc9e1ccbcf880b)) +* add quest toggle mode functionality ([c5bfb18](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c5bfb18b2d434583133b45d91955bde902fa2456)) +* add time service ([d89d9f7](https://github.com/DorielRivalet/MHFZ_Overlay/commit/d89d9f7faa545e251c9d039ceb7a2aaca93c1e26)) +* add watermark modes options ([c513fe7](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c513fe761e90d1bf8ab047d27dfe35e04882a986)), closes [DorielRivalet/mhfz-overlay#171](https://github.com/DorielRivalet/mhfz-overlay/issues/171) +* **assets:** add gauntlet icons ([23579c4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/23579c489b43be26dc2051ed8e775f9e9da2f793)) +* **assets:** add images ([e7d480b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e7d480b8535907b33d5994f4e1794d709a2142d9)) +* **audio:** add challenge sound effects ([4b786ac](https://github.com/DorielRivalet/MHFZ_Overlay/commit/4b786ac586074777f8eb14ab17f5072eb07d2da4)) +* **audio:** add sound effects files ([87b2356](https://github.com/DorielRivalet/MHFZ_Overlay/commit/87b235671bcf0ca985ec864142938faea4169ae3)) +* **audio:** implement audio configuration ([f3cd221](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f3cd221d52bd1e9eef5cbd9f55bc1282eaf4cff5)), closes [DorielRivalet/mhfz-overlay#162](https://github.com/DorielRivalet/mhfz-overlay/issues/162) +* **benchmark:** add benchmarks ([7ded0ff](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7ded0ff75fcf3dadf8f3a05d01e4949ba674ec80)) +* **bingo:** add bingo buttons reactivity ([64dcc1a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/64dcc1a646dad2cbddfe2c00a8ac4b7826983efc)) +* **bingo:** add bingo cell model ([f410d9d](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f410d9d9687e0b79f91e3541ade2d0590620f705)) +* **bingo:** add bingo data structures ([8e7057b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8e7057bad0f0c02b89b0a12f6f598072a6e53486)) +* **bingo:** add converters ([dd65157](https://github.com/DorielRivalet/MHFZ_Overlay/commit/dd65157d5d6890847ceba37a30ef812a5e434b80)) +* **bingo:** add snackbar ([b285192](https://github.com/DorielRivalet/MHFZ_Overlay/commit/b2851920f324bd70b05820f7fa4b3d5267cebd0c)) +* **bingo:** add true transcend gauge ([c7f8f4b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c7f8f4b4ddbd6ce362533287b47653af7f77b5b0)) +* **bingo:** adjust monster base score ([52134ad](https://github.com/DorielRivalet/MHFZ_Overlay/commit/52134adc25eee2e5e796974fcfa7cf7eddf87471)) +* **bingo:** expand bingo functionality ([d4b966d](https://github.com/DorielRivalet/MHFZ_Overlay/commit/d4b966dbde24bbc59655c23f956309a8432176ff)) +* **bingo:** expand monster collection ([b4edf7c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/b4edf7cb81176bd0dee1c1d6832e67f2ee378329)) +* **bingo:** generate bingo board view ([bef2c53](https://github.com/DorielRivalet/MHFZ_Overlay/commit/bef2c533a787669e02f249dad5261253f22b23dd)) +* **bingo:** update bingo window layout ([ccf7a19](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ccf7a19519f382407d90e79d78f286198bfde9c0)) +* **bingo:** update models ([bcbac76](https://github.com/DorielRivalet/MHFZ_Overlay/commit/bcbac764aa206695e46d509176a5bd021755a094)) +* **bingo:** update tab layout ([fb09a35](https://github.com/DorielRivalet/MHFZ_Overlay/commit/fb09a35f28d6084afd709a13e8c50a9711eb449a)) +* **bitfields:** add gauntlet boost ([2b50a3a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2b50a3a2c8e544dad7bdf2c683eee53dd6535f51)) +* **bitfields:** add more bitfields ([ab8099e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ab8099e865c4b21143451f3b1d350af70349f573)) +* **challenge:** add challenge states ([ff1540b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ff1540b066bef37d7472be3d090af3b98f65a1d4)) +* **challenges:** add book of secrets data ([6db07d1](https://github.com/DorielRivalet/MHFZ_Overlay/commit/6db07d1b19db221ba452fbb8d1b9fd546d6ba28e)) +* **challenges:** add challenges unlock button ([458f9ae](https://github.com/DorielRivalet/MHFZ_Overlay/commit/458f9ae01649074224b83e1322cf5e1e76c59d6d)) +* **challenges:** add check for challenge window ([81c4410](https://github.com/DorielRivalet/MHFZ_Overlay/commit/81c44107b27facc2d207135988e10c63e30b6177)) +* **challenges:** add inventory system ([f1fb95d](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f1fb95d036a8d1c6f86c92d44629d10056b836f0)) +* **challenges:** add sky corridor to collection ([eebe66e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/eebe66e465405d11cdc85613a5383ac309240434)) +* **challenges:** implement challenge unlock ([cddc2e2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/cddc2e242e3c6aa8aa979f8a2da522812b7dc6fb)) +* **challenges:** update challenges explanation ([11794f4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/11794f4bdc14499000c7b17bb79adc6b0cbfd58f)) +* **challenges:** update dragon parts description ([aa6d19e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/aa6d19e6b1fa163d9523ebf44a4458760fdf00d5)) +* **challenge:** update ancient dragon parts ([4886f58](https://github.com/DorielRivalet/MHFZ_Overlay/commit/4886f58c366ae2c3b68c7c7e30d6aef4e64c9960)) +* change default settings values ([99b99cc](https://github.com/DorielRivalet/MHFZ_Overlay/commit/99b99cc60b2b8f4197a86ac7b25eb320da2a17b4)) +* **configuration:** add audio section ([8ca2272](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8ca2272b5e9f43226fea12ee8c8e7acca75137a9)) +* **configuration:** add missing overlay restart icons ([a7e0de8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/a7e0de8c8d9de15f5e52f6137b772817ccc961f5)) +* **configuration:** add missing restart requirement ([a7d05eb](https://github.com/DorielRivalet/MHFZ_Overlay/commit/a7d05ebe95403adbe107c541e8809a6cf09bfbc9)) +* disable challenges with early return ([7a2ae3f](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7a2ae3f4718d99fc3567023ffbb92c1ae2139630)) +* **discord:** add UL/HC ([812072c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/812072c6511380260f68755b0c30e7e4819279d2)) +* expand bingo functionality ([bd733ab](https://github.com/DorielRivalet/MHFZ_Overlay/commit/bd733ab3f15967ca9a3147d57f2d436904406272)) +* **github:** update bug report template ([cab623b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/cab623b06de8c0f9ce495973c34c1f01146079c5)) +* make invariant cultureinfo default ([16c2d45](https://github.com/DorielRivalet/MHFZ_Overlay/commit/16c2d4570faffcd4e1d26d4db10aaec6c32a1d8e)) +* **rakefile:** change current working directory ([61e59c4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/61e59c49564cebf761b38ad120087040bad4cab2)) +* **scripts:** add file and folder checks in lua script ([c3d6b33](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c3d6b334379f21385b3fddec5e338f7f164efc58)) +* **scripts:** print success message ([ba0f6aa](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ba0f6aa16e2667e2dbc14aacd11155bd3949ce9b)) +* **scripts:** show current working directory in python ([04b3767](https://github.com/DorielRivalet/MHFZ_Overlay/commit/04b37679bded9bb0feee1a4ad364503571da7dea)) +* **scripts:** show present working directory ([31f8a13](https://github.com/DorielRivalet/MHFZ_Overlay/commit/31f8a13b2861c6fcb9aaed6816c16614ea8d59c3)) +* update ancient dragon parts ([3bf4544](https://github.com/DorielRivalet/MHFZ_Overlay/commit/3bf45446b4e2b68ef5a9f82d32d5062e2cee11f8)) + + +### Bug Fixes + +* **assets:** armor sphere icon ([2eb964c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2eb964ca5fda587473d6eb30a5d1eb55e83cb68f)) +* binding failures ([eefc246](https://github.com/DorielRivalet/MHFZ_Overlay/commit/eefc246b21e29dedba38a8299ef39fcf6852fb49)) +* code paths ([31a15e5](https://github.com/DorielRivalet/MHFZ_Overlay/commit/31a15e539aa7f6278ebc8eeac600d0c372dce3e6)) +* custom quest id showing when disabled in settings ([0aa9187](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0aa9187f4e369b52b83f2b54fdbe8dc7691af7ef)) +* monster 2 hp bar visibility ([cedcea3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/cedcea3dcd5c6694f6db9d3ff9423ccb5ec6c617)) +* quest time percent ([97b603b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/97b603b376a429d0b7d8b66208c3e30ba2d4b218)) +* **rakefile:** change current working directory ([9fd1bed](https://github.com/DorielRivalet/MHFZ_Overlay/commit/9fd1bed1604b58c8b5186d7c4797c8945a998132)) +* remove warnings ([4d233e9](https://github.com/DorielRivalet/MHFZ_Overlay/commit/4d233e9aea9b0b03fd30070c5313a8b70d91156c)) +* **scripts:** change makefile paths ([085f229](https://github.com/DorielRivalet/MHFZ_Overlay/commit/085f229b3366cab6333a6e513abdbbd7e7691c0f)) +* **scripts:** change paths in lua ([ece2702](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ece270296d512174af64dcdc42e9e6e1bcc8a0b6)) +* **scripts:** fix lua file ([84eeabf](https://github.com/DorielRivalet/MHFZ_Overlay/commit/84eeabf77e51c08fe83e0f0f4bafbed25e58da9f)) +* **scripts:** makefile whitespace ([3d5fb21](https://github.com/DorielRivalet/MHFZ_Overlay/commit/3d5fb215f9da06f9318847c254437c2c179bd921)) +* **sqlite:** date formatting ([60e1bbb](https://github.com/DorielRivalet/MHFZ_Overlay/commit/60e1bbb04ebbc2104722585e101e119a93d4586a)), closes [DorielRivalet/mhfz-overlay#168](https://github.com/DorielRivalet/mhfz-overlay/issues/168) +* tab icons being clickable ([3cc2c24](https://github.com/DorielRivalet/MHFZ_Overlay/commit/3cc2c2441d9feeb3f2873c6ced93fba117819edf)) +* timer methods ([5e5b50b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/5e5b50b912f0186b59d1e324e406176b5a1ed0ff)) +* timers ([12921bb](https://github.com/DorielRivalet/MHFZ_Overlay/commit/12921bbb4f95d083d0288e1d0eab79cb4c8aea69)), closes [DorielRivalet/mhfz-overlay#172](https://github.com/DorielRivalet/mhfz-overlay/issues/172) [DorielRivalet/mhfz-overlay#170](https://github.com/DorielRivalet/mhfz-overlay/issues/170) [DorielRivalet/mhfz-overlay#169](https://github.com/DorielRivalet/mhfz-overlay/issues/169) + + +### Performance Improvements + +* add timer benchmarks ([26c071b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/26c071bbf2c0e450525d21c1321a21411dbb8698)) +* remove gifs ([1e5f4b7](https://github.com/DorielRivalet/MHFZ_Overlay/commit/1e5f4b713f52f45cb0ea35e0b1496bb680d83df9)) +* use timespan timer ([0766420](https://github.com/DorielRivalet/MHFZ_Overlay/commit/076642094da78f217bcb573e9c4550031c3e8bd6)) + + +### For Developers + +* add artifacts ([0135ac3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0135ac311fa41dc0b4902d3870a874df95c64dc7)) +* add image artifacts for git stats workflow ([e80f6c2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e80f6c20065903dfdfe0c1310fc43b0dd1ee1ef9)) +* add project for benchmark ([bf868c4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/bf868c4ebc8ce2734bbfed18c3e20e038f705c90)) +* add rakefile for github actions ([0d0e5b2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0d0e5b2381b5308cde9690e0f75dee054795fdb4)) +* add stackoverflow link ([37e2c77](https://github.com/DorielRivalet/MHFZ_Overlay/commit/37e2c77cc77a644bfb1619e251b5ec228cb3c0b9)) +* **bingo:** implement interfaces ([17e8f47](https://github.com/DorielRivalet/MHFZ_Overlay/commit/17e8f47e2ff4c8106d42e19b87b72f4a4c677b4b)) +* bump version ([7ec3067](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7ec3067fbe951b176126acfd722c592e786fab4c)) +* change target framework ([e4de71d](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e4de71d870bf60e45f9f535470b1cd87fda8a50e)) +* **deps-dev:** bump @commitlint/cli from 17.6.7 to 17.7.1 ([2ee78bd](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2ee78bd864630b5fd1f873a7f9eb34e9e62cc5f1)) +* **deps-dev:** bump @commitlint/config-conventional ([6aec219](https://github.com/DorielRivalet/MHFZ_Overlay/commit/6aec2199b5391731808369113d7e2a8fbd6fa091)) +* **deps-dev:** bump alex from 11.0.0 to 11.0.1 ([893fcd1](https://github.com/DorielRivalet/MHFZ_Overlay/commit/893fcd18caf4344713a7f89e4b3821d2cd7f53f0)) +* **deps:** add dependency management files ([a90a463](https://github.com/DorielRivalet/MHFZ_Overlay/commit/a90a4639f081d9fc7fc9badc593e29649976c5bf)) +* **deps:** bump actions/cache from 2 to 3 ([c65cbdb](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c65cbdb1fe96824e048ad69c13a09d0526484027)) +* **deps:** bump actions/checkout from 2 to 3 ([ee3ce20](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ee3ce2006a47cb5e6cb556c4c5f72ffb4d2928a1)) +* **deps:** bump actions/checkout from 2 to 3 ([698d961](https://github.com/DorielRivalet/MHFZ_Overlay/commit/698d961d8e72df54f427b93a4fc387e3c52d5bc6)) +* **deps:** bump actions/setup-dotnet from 1 to 3 ([b3041e9](https://github.com/DorielRivalet/MHFZ_Overlay/commit/b3041e97bf75dbc6b67a494a39f2197ed2b21467)) +* **deps:** bump LiveChartsCore.SkiaSharpView.WPF in /MHFZ_Overlay ([f7120a8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f7120a80c33c059de23b0ca21ff3bfd7ac646d40)) +* **deps:** bump LiveChartsCore.SkiaSharpView.WPF in /MHFZ_Overlay ([1238958](https://github.com/DorielRivalet/MHFZ_Overlay/commit/1238958bccbcc616d14dbd2a5e353f6b15ea6ab9)) +* **deps:** bump Microsoft.Web.WebView2 in /MHFZ_Overlay ([60554ac](https://github.com/DorielRivalet/MHFZ_Overlay/commit/60554acd6a453386387a9a6b8d036ead76ad99c9)) +* **deps:** bump NLog from 5.2.2 to 5.2.3 in /MHFZ_Overlay ([30973d5](https://github.com/DorielRivalet/MHFZ_Overlay/commit/30973d53a80af307c102d751ec57f12f0a375753)) +* **deps:** bump NuGet.CommandLine from 6.6.1 to 6.7.0 in /MHFZ_Overlay ([eca3e58](https://github.com/DorielRivalet/MHFZ_Overlay/commit/eca3e58f7f295395fcf9cdab9e6188ebf6f80e0c)) +* **deps:** bump release-it and @release-it/conventional-changelog ([c54482e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c54482e5cc3f96139ccdcafe2f463b2e02f07967)) +* **deps:** bump release-it from 16.1.3 to 16.1.4 ([69bbfcb](https://github.com/DorielRivalet/MHFZ_Overlay/commit/69bbfcb7f3ad624fc6222ee5d815688d0b9aa8ff)) +* **deps:** bump release-it from 16.1.4 to 16.1.5 ([b5b23a2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/b5b23a2be9187320c8e7aec9fb684b4310ff47be)) +* **deps:** bump WPF-UI in /MHFZ_Overlay ([c46a078](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c46a0784eff2e48abc8f9ce7d1303fccaa9b0f76)) +* **deps:** update EZlion ([368c899](https://github.com/DorielRivalet/MHFZ_Overlay/commit/368c899cf03093e5feb1870dfc67dc58343f3cdf)) +* **FAQ:** add LaTeX ([98c24dd](https://github.com/DorielRivalet/MHFZ_Overlay/commit/98c24ddace737fc1bd7773a90ddf1847c07baa50)) +* **FAQ:** fix LaTeX formatting ([f724995](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f724995334a022e1fa629498d3cc3446bd62e8d7)) +* **FAQ:** separate math blocks ([37eb8f3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/37eb8f312bae8ac4e5ecb3e8aa6d41428112558a)) +* **FAQ:** update FAQ.md ([2aa4d2a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2aa4d2a36c654d7f85a3b81c54b62d14b1732bb7)) +* fix dotnet workflow ([6dd4241](https://github.com/DorielRivalet/MHFZ_Overlay/commit/6dd424128358479e4873da70e423599ba595570d)) +* give example data flow ([f6ea1c0](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f6ea1c094d415c5398931bfccd2751767919b2a2)) +* **makefile:** add makefile for git stats workflow ([f438669](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f43866941ac90a1a8508790eb91255f0b345d331)) +* **nuget:** update Wpf.Ui ([f74cc5e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f74cc5eef849a7f4a44f0e0832cfec8c2643fde3)) +* **performance:** add benchmarks location ([3dd8ff4](https://github.com/DorielRivalet/MHFZ_Overlay/commit/3dd8ff4bfe779768cc0ba752c2afd9dd3b601336)) +* **README:** add configuration preset image ([92a5aa0](https://github.com/DorielRivalet/MHFZ_Overlay/commit/92a5aa02c3970ee5dc2bacde38d2fd0f4489219e)) +* **README:** add git stats images ([8161f58](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8161f58b79033a569e9bdb9964ffe3e985039898)) +* **README:** create github actions workflow status badge ([a2b2b97](https://github.com/DorielRivalet/MHFZ_Overlay/commit/a2b2b97c5165cdaa641a685495aad18a2ee2c648)) +* **README:** update acknowledgements ([80336d5](https://github.com/DorielRivalet/MHFZ_Overlay/commit/80336d557090dcebee04be4f330b2bf7f44f0b82)) +* **README:** update quick troubleshooting section ([31d1b41](https://github.com/DorielRivalet/MHFZ_Overlay/commit/31d1b419779514af882746065fd0092103c3b57d)) +* **README:** update README.md ([cb4f2fc](https://github.com/DorielRivalet/MHFZ_Overlay/commit/cb4f2fcd83c8cd964ca34abcc64e5295392193e6)) +* **README:** update README.md ([039d921](https://github.com/DorielRivalet/MHFZ_Overlay/commit/039d9218b9a8b2c9625aeaafc868e6c9bdd0ea61)) +* **README:** update README.md ([2cce55c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2cce55c4a1aba7ae78e4d1047390fdbd85b9ce28)) +* remove stopwatches ([5eaef16](https://github.com/DorielRivalet/MHFZ_Overlay/commit/5eaef16b681d79cfc2157fe7912a675526e62398)) +* **scripts:** add license header ([18cb9a3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/18cb9a386b217274afef7567f3766fe50368e234)) +* **scripts:** update git statistics images ([0f0cea0](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0f0cea088cc99bff0b3c12263e79734ad9113e96)) +* **scripts:** update git statistics images ([8ce9b97](https://github.com/DorielRivalet/MHFZ_Overlay/commit/8ce9b97bd096118b94fff7c350002a20947d836b)) +* **scripts:** update git statistics images ([4888bf3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/4888bf37cbec7bd09c6faf9ac65ff1bc2d10c8b4)) +* **scripts:** update git statistics images ([b17f551](https://github.com/DorielRivalet/MHFZ_Overlay/commit/b17f551e426b83312836296748d47f47f620b204)) +* **scripts:** update git statistics images ([0b1c533](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0b1c5334b8004fa0c12d63de00c2fbc6f40229f4)) +* **scripts:** update git statistics images ([c287833](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c287833e00716d11d2f296ff5ba8b6e6c08534fc)) +* **scripts:** update README.md ([4544080](https://github.com/DorielRivalet/MHFZ_Overlay/commit/45440804397ef5670413d83baf4052564022286d)) +* update git stats workflow ([226b30e](https://github.com/DorielRivalet/MHFZ_Overlay/commit/226b30e2c314b983703b3bf28a3beb50045e323b)) +* update git stats workflow ([44bfd6f](https://github.com/DorielRivalet/MHFZ_Overlay/commit/44bfd6f94d9e956b00fdd92ef0f3d8192e4af509)) +* update git stats workflow ([74d98b8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/74d98b8a2adb7370ff4d67dcc6ee8f762338a797)) +* update git stats workflow ([3516767](https://github.com/DorielRivalet/MHFZ_Overlay/commit/35167677f09c7b6baa9145702228bedb45aed079)) +* update git stats workflow ([ab79a8f](https://github.com/DorielRivalet/MHFZ_Overlay/commit/ab79a8f3209c094330e77e074a3ecba1a7251a24)) +* update git stats workflow ([5b86044](https://github.com/DorielRivalet/MHFZ_Overlay/commit/5b86044b07dd03a52d081bc89cc11399ac76020d)) +* update git stats workflow ([3c1cffc](https://github.com/DorielRivalet/MHFZ_Overlay/commit/3c1cffca19b57b892b69f405d1992a66428eb2ad)) +* update git stats workflow ([bc8ca67](https://github.com/DorielRivalet/MHFZ_Overlay/commit/bc8ca67bc0ef46a79e17e940433fff345b36853d)) +* update git stats workflow ([184b52c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/184b52c78a73719bda2de57708a0bd7ae2ba9ab1)) +* update git stats workflow ([01c8f3b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/01c8f3bdf7c7b501df3e7aa8121c2f5b0579e89b)) +* update git stats workflow ([617cfee](https://github.com/DorielRivalet/MHFZ_Overlay/commit/617cfeea985575f9803976b162e612b9f78e5876)) +* update git stats workflow ([c74e80c](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c74e80c8f47680d5140e66a091a39f31f0c09271)) +* update git stats workflow ([e9cb6ae](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e9cb6aeeecdda2ccfd7a59677d21947716b38719)) +* update git stats workflow ([32be920](https://github.com/DorielRivalet/MHFZ_Overlay/commit/32be920ff62a21c6fa9c023871253846b948a86c)) +* update git stats workflow ([c82e1a5](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c82e1a58abcceb823c8ee0d677a0256665710a64)) +* update git stats workflow ([14a2301](https://github.com/DorielRivalet/MHFZ_Overlay/commit/14a2301cd60e59eacd6e6801c72e70500ae281b2)) +* update git stats workflow ([27e96f8](https://github.com/DorielRivalet/MHFZ_Overlay/commit/27e96f8862e5acd2af90aaf469905cbac032d3a5)) +* update git stats workflow ([e809f21](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e809f21788a79ef782c8fd5624b2b4340985c505)) +* update git stats workflow ([c11fb4d](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c11fb4d767d861cc82168a1e71b61fdedfdc0423)) +* update git stats workflow ([04c7341](https://github.com/DorielRivalet/MHFZ_Overlay/commit/04c73419ee51597c0ce7977ecf5eeb0951c5d824)) +* update git stats workflow ([c5db62a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/c5db62affee9c53e1363b6b0006492d542459d70)) +* update git stats workflow ([52c7e9b](https://github.com/DorielRivalet/MHFZ_Overlay/commit/52c7e9bde862c1e7a0ee6185fe767ef2af1687be)) +* update git stats workflow ([f056bfa](https://github.com/DorielRivalet/MHFZ_Overlay/commit/f056bfa62011a1af0ff3190b5cf8468ebf3b1cd6)) +* update git stats workflow ([27f359a](https://github.com/DorielRivalet/MHFZ_Overlay/commit/27f359a974084e9ba91c4e55290f3b04fdc6f716)) +* update git stats workflow ([4807c44](https://github.com/DorielRivalet/MHFZ_Overlay/commit/4807c4418e0acc7fa35573b833e5e47c19f39b63)) +* update git stats workflow ([7c473a3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7c473a3950ed076f60fd35a623c3ea4b6701da7c)) +* update git stats workflow ([e2670a2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/e2670a2fb9b82f01f57ed4582cf564ab035a09b9)) +* update git stats workflow ([0d75d44](https://github.com/DorielRivalet/MHFZ_Overlay/commit/0d75d4471bccd603327da52253d1c359fc4db97a)) +* update git stats workflow ([fe97bf2](https://github.com/DorielRivalet/MHFZ_Overlay/commit/fe97bf203bc335fbd605298edf0b0c92fb3dfa28)) +* update git stats workflow ([2ab7dfd](https://github.com/DorielRivalet/MHFZ_Overlay/commit/2ab7dfdb975d97d1f3d3b7f1b7570062bcb4f558)) +* update git stats workflow ([076a686](https://github.com/DorielRivalet/MHFZ_Overlay/commit/076a686d60e0897c0f0acf96296ac59d05dbf369)) +* update git stats workflow ([35ba0b3](https://github.com/DorielRivalet/MHFZ_Overlay/commit/35ba0b3e736cf76201b7585dec9a880841cb9226)) +* update git stats workflow ([d2e4f67](https://github.com/DorielRivalet/MHFZ_Overlay/commit/d2e4f67473bff8c9f9e8e5780cfe22c1139152d9)) +* update git stats workflow ([9a44a84](https://github.com/DorielRivalet/MHFZ_Overlay/commit/9a44a84eff0c96694b0ae932f9d19ee502c5af3f)) +* update sonarcloud java version ([7edabff](https://github.com/DorielRivalet/MHFZ_Overlay/commit/7edabfff7146bad989dc43f666e143f7f10878e2)) +* update workflows ([acd7205](https://github.com/DorielRivalet/MHFZ_Overlay/commit/acd72055fb3a1e2b3658b73d737536325b34393c)) + ## [0.29.1](https://github.com/DorielRivalet/MHFZ_Overlay/compare/v0.29.0...v0.29.1) (2023-08-03) diff --git a/FAQ.md b/FAQ.md index 30ff8d65..aac60baa 100644 --- a/FAQ.md +++ b/FAQ.md @@ -169,6 +169,7 @@ The location of any previous settings are in the subfolders of `%LocalAppData%/D 5. Drag and drop the file. 6. Copy/Paste into Output Comparer the hash provided by the developer. 7. You should get the message `The hashes are the same.`. +8. Close DevToys. ### Command Line Interface @@ -392,9 +393,12 @@ As an added bonus: - Effective HP is the HP taking into account the monster's defense rate. Burning Freezing Elzelion has 1,000,000 EHP because his True HP is 30,000 and his defense rate is 0.03. -```text -Effective HP = True HP / Defense rate -1,000,000 = 30,000 / 0.03 +```math +Effective HP = \frac{True HP}{DefenseRate} +``` + +```math +1,000,000 = \frac{30,000}{0.03} ``` - True HP is the HP of the monster without taking into account the monster's defense rate. diff --git a/MHFZOverlayBenchmark/Comparisons/TimerComparison.cs b/MHFZOverlayBenchmark/Comparisons/TimerComparison.cs new file mode 100644 index 00000000..dfb7cd0c --- /dev/null +++ b/MHFZOverlayBenchmark/Comparisons/TimerComparison.cs @@ -0,0 +1,149 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZOverlayBenchmark.Comparisons; + +using BenchmarkDotNet.Attributes; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.Intrinsics.Arm; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +public enum TimerFormat +{ + MinutesSeconds, + MinutesSecondsMilliseconds, + HoursMinutesSeconds, +} + +public enum TimerMode +{ + TimeLeft, + Elapsed, +} + +// TODO optimize +[RPlotExporter] +[MedianColumn, MinColumn, MaxColumn] +[UnicodeConsoleLogger] +[MemoryDiagnoser] +[ExceptionDiagnoser] +public class TimerComparison +{ + public const decimal FramesPerSecond = 30; + + public string TimeLeftPercentNumber = " (100%)"; + + [Params(120 * 60 * 30)] + public int TimeDefInt; + + [Benchmark] + public string BenchmarkSimpleTimer() + { + string s = string.Empty; + for (int i = TimeDefInt; i > 0; i--) + { + s = SimpleTimer(i, TimerFormat.MinutesSecondsMilliseconds, true, TimeDefInt, true, TimeLeftPercentNumber, TimerMode.Elapsed); + }; + + return s; + } + + [Benchmark] + public string BenchmarkTimeSpanTimer() + { + string s = string.Empty; + for (int i = TimeDefInt; i > 0; i--) + { + s = TimeSpanTimer(i, TimerFormat.MinutesSecondsMilliseconds, true, TimeDefInt, true, TimeLeftPercentNumber, TimerMode.Elapsed); + }; + + return s; + } + + [Benchmark] + public string BenchmarkStringBuilderTimer() + { + string s = string.Empty; + for (int i = TimeDefInt; i > 0; i--) + { + s = StringBuilderTimer(i, TimerFormat.MinutesSecondsMilliseconds, true, TimeDefInt, true, TimeLeftPercentNumber, TimerMode.Elapsed); + }; + + return s; + } + + public string SimpleTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + // TODO wrong conditionals for timeint >= timedefint? + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? FramesPerSecond : 1; + decimal milliseconds = time / framesPerSecond * 1000; + decimal totalMinutes = Math.Floor(milliseconds / 60000); + decimal minutes = totalMinutes >= 60 ? totalMinutes : Math.Floor(milliseconds / 60000); + decimal seconds = Math.Floor((milliseconds - (minutes * 60000)) / 1000); + decimal remainingMilliseconds = milliseconds - (minutes * 60000) - (seconds * 1000); + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + return timerFormat switch + { + TimerFormat.MinutesSeconds => $"{minutes:00}:{seconds:00}" + timeLeftPercent, + TimerFormat.MinutesSecondsMilliseconds => $"{minutes:00}:{seconds:00}.{remainingMilliseconds:000}" + timeLeftPercent, + _ => $"{minutes:00}:{seconds:00}.{remainingMilliseconds:000}" + timeLeftPercent, + }; + } + + public string StringBuilderTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? FramesPerSecond : 1; + decimal totalSeconds = time / framesPerSecond; + decimal totalMinutes = Math.Floor(totalSeconds / 60); + decimal minutes = totalMinutes >= 60 ? totalMinutes : Math.Floor(totalSeconds / 60); + decimal seconds = Math.Floor(totalSeconds % 60); + decimal milliseconds = Math.Round((time % framesPerSecond) * (1000M / framesPerSecond)); + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + StringBuilder sb = new StringBuilder(); + switch (timerFormat) + { + default: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", minutes, seconds, milliseconds); + break; + case TimerFormat.MinutesSeconds: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}", minutes, seconds); + break; + case TimerFormat.MinutesSecondsMilliseconds: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", minutes, seconds, milliseconds); + break; + } + + sb.Append(timeLeftPercent); + return sb.ToString(); + } + + public string TimeSpanTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? FramesPerSecond : 1; + decimal timeInSeconds = time / framesPerSecond; + TimeSpan timeInSecondsSpan = TimeSpan.FromSeconds((double)timeInSeconds); + int roundedMilliseconds = (int)(Math.Round(timeInSecondsSpan.TotalMilliseconds) % 1000); + var totalMinutes = Math.Floor(timeInSecondsSpan.TotalSeconds / 60); + var minutes = totalMinutes >= 60 ? totalMinutes : timeInSecondsSpan.Minutes; + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + // Format the TimeSpan object as a string + return timerFormat switch + { + TimerFormat.MinutesSeconds => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}" + timeLeftPercent, + TimerFormat.MinutesSecondsMilliseconds => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}.{roundedMilliseconds:000}" + timeLeftPercent, + _ => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}.{roundedMilliseconds:000}" + timeLeftPercent, + }; + } +} diff --git a/MHFZOverlayBenchmark/MHFZOverlayBenchmark.csproj b/MHFZOverlayBenchmark/MHFZOverlayBenchmark.csproj new file mode 100644 index 00000000..62178873 --- /dev/null +++ b/MHFZOverlayBenchmark/MHFZOverlayBenchmark.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/MHFZOverlayBenchmark/Program.cs b/MHFZOverlayBenchmark/Program.cs new file mode 100644 index 00000000..d7c082a2 --- /dev/null +++ b/MHFZOverlayBenchmark/Program.cs @@ -0,0 +1,18 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +// See https://aka.ms/new-console-template for more information +using BenchmarkDotNet.Running; +using System.Management; +using MHFZOverlayBenchmark.Comparisons; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Reports; +using Perfolizer.Horology; +using BenchmarkDotNet.Columns; + +var summary = BenchmarkRunner.Run( + DefaultConfig.Instance.WithSummaryStyle( + SummaryStyle.Default + .WithTimeUnit(TimeUnit.Millisecond) + .WithSizeUnit(SizeUnit.MB))); diff --git a/MHFZ_Overlay.sln b/MHFZ_Overlay.sln index a3514998..7dc871bf 100644 --- a/MHFZ_Overlay.sln +++ b/MHFZ_Overlay.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MHFZ_Overlay", "MHFZ_Overla EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MHFZ_OverlayTest", "MHFZ_OverlayTest\MHFZ_OverlayTest.csproj", "{13ED7686-F93C-4DBC-9258-061DE45FFC60}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MHFZOverlayBenchmark", "MHFZOverlayBenchmark\MHFZOverlayBenchmark.csproj", "{7A166AB3-8381-45A7-A827-AD42A9EDD863}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +23,10 @@ Global {13ED7686-F93C-4DBC-9258-061DE45FFC60}.Debug|Any CPU.Build.0 = Debug|Any CPU {13ED7686-F93C-4DBC-9258-061DE45FFC60}.Release|Any CPU.ActiveCfg = Release|Any CPU {13ED7686-F93C-4DBC-9258-061DE45FFC60}.Release|Any CPU.Build.0 = Release|Any CPU + {7A166AB3-8381-45A7-A827-AD42A9EDD863}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {7A166AB3-8381-45A7-A827-AD42A9EDD863}.Debug|Any CPU.Build.0 = Release|Any CPU + {7A166AB3-8381-45A7-A827-AD42A9EDD863}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A166AB3-8381-45A7-A827-AD42A9EDD863}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MHFZ_Overlay/App.config b/MHFZ_Overlay/App.config index a63b4323..26b7d10c 100644 --- a/MHFZ_Overlay/App.config +++ b/MHFZ_Overlay/App.config @@ -689,7 +689,7 @@ 180 - True + False False @@ -954,6 +954,42 @@ False + + 0.5 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Final + + + Automatic + + + True + diff --git a/MHFZ_Overlay/App.xaml b/MHFZ_Overlay/App.xaml index e82b5d0d..affecfdb 100644 --- a/MHFZ_Overlay/App.xaml +++ b/MHFZ_Overlay/App.xaml @@ -162,7 +162,7 @@ https://stackoverflow.com/questions/3425720/xaml-the-property-resources-is-set-more-than-once#3425956 --> - + diff --git a/MHFZ_Overlay/App.xaml.cs b/MHFZ_Overlay/App.xaml.cs index 45ba9dd0..e7617d20 100644 --- a/MHFZ_Overlay/App.xaml.cs +++ b/MHFZ_Overlay/App.xaml.cs @@ -11,9 +11,11 @@ namespace MHFZ_Overlay; using System.Globalization; using System.IO; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Interop; +using System.Windows.Markup; using System.Windows.Media; using System.Windows.Threading; using MHFZ_Overlay.Models.Constant; @@ -93,12 +95,26 @@ protected override void OnStartup(StartupEventArgs e) // Start the stopwatch stopwatch.Start(); + + // https://stackoverflow.com/questions/12729922/how-to-set-cultureinfo-invariantculture-default + CultureInfo culture = CultureInfo.InvariantCulture; + + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + + CultureInfo.DefaultThreadCurrentCulture = culture; + CultureInfo.DefaultThreadCurrentUICulture = culture; + + FrameworkElement.LanguageProperty.OverrideMetadata( + typeof(FrameworkElement), + new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name))); + var loggingRules = NLog.LogManager.Configuration.LoggingRules; var s = (Settings)Current.TryFindResource("Settings"); loggingRules[0].SetLoggingLevels(LoggingService.GetLogLevel(s.LogLevel), NLog.LogLevel.Fatal); Logger.Info(CultureInfo.InvariantCulture, "Started WPF application"); Logger.Trace(CultureInfo.InvariantCulture, "Call stack: {0}", new StackTrace().ToString()); - Logger.Debug("OS: {0}, is64BitOS: {1}, is64BitProcess: {2}, CLR version: {3}", Environment.OSVersion, Environment.Is64BitOperatingSystem, Environment.Is64BitProcess, Environment.Version); + Logger.Debug(CultureInfo.InvariantCulture, "OS: {0}, is64BitOS: {1}, is64BitProcess: {2}, CLR version: {3}", Environment.OSVersion, Environment.Is64BitOperatingSystem, Environment.Is64BitProcess, Environment.Version); // TODO: test if this doesnt conflict with squirrel update CurrentProgramVersion = $"v{GetAssemblyVersion}"; @@ -143,14 +159,14 @@ protected override void OnStartup(StartupEventArgs e) private static void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) => // Log/inspect the inspection here - Logger.Error("Unhandled exception\n\nMessage: {0}\n\nStack Trace: {1}\n\nHelp Link: {2}\n\nHResult: {3}\n\nSource: {4}\n\nTarget Site: {5}", e.Exception.Message, e.Exception.StackTrace, e.Exception.HelpLink, e.Exception.HResult, e.Exception.Source, e.Exception.TargetSite); + Logger.Error(CultureInfo.InvariantCulture, "Unhandled exception\n\nMessage: {0}\n\nStack Trace: {1}\n\nHelp Link: {2}\n\nHResult: {3}\n\nSource: {4}\n\nTarget Site: {5}", e.Exception.Message, e.Exception.StackTrace, e.Exception.HelpLink, e.Exception.HResult, e.Exception.Source, e.Exception.TargetSite); private static void SetRenderingMode(string renderingMode) { RenderOptions.ProcessRenderMode = renderingMode == "Hardware" ? RenderMode.Default : RenderMode.SoftwareOnly; - Logger.Info($"Rendering mode: {renderingMode}"); + Logger.Info(CultureInfo.InvariantCulture, $"Rendering mode: {renderingMode}"); } // https://github.com/Squirrel/Squirrel.Windows/issues/198#issuecomment-299262613 diff --git a/MHFZ_Overlay/Assets/Background/10.png b/MHFZ_Overlay/Assets/Background/10.png new file mode 100644 index 00000000..cb79c434 Binary files /dev/null and b/MHFZ_Overlay/Assets/Background/10.png differ diff --git a/MHFZ_Overlay/Assets/Background/background_noise.png b/MHFZ_Overlay/Assets/Background/background_noise.png new file mode 100644 index 00000000..47ae62db Binary files /dev/null and b/MHFZ_Overlay/Assets/Background/background_noise.png differ diff --git a/MHFZ_Overlay/Assets/Background/background_pattern1.png b/MHFZ_Overlay/Assets/Background/background_pattern1.png new file mode 100644 index 00000000..8cd02495 Binary files /dev/null and b/MHFZ_Overlay/Assets/Background/background_pattern1.png differ diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonAlacrityMantle.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonAlacrityMantle.xaml new file mode 100644 index 00000000..43d07140 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonAlacrityMantle.xaml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonCognisphere.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonCognisphere.xaml new file mode 100644 index 00000000..8d9ad92e --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonCognisphere.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonConquerorsCloak.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonConquerorsCloak.xaml new file mode 100644 index 00000000..a2ba384e --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonConquerorsCloak.xaml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonCranium.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonCranium.xaml new file mode 100644 index 00000000..58c75443 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonCranium.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonDaoraPlating.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonDaoraPlating.xaml new file mode 100644 index 00000000..3f26a5f9 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonDaoraPlating.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonEmpyreanAura.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonEmpyreanAura.xaml new file mode 100644 index 00000000..70aa4b39 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonEmpyreanAura.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonEtherealMatrix.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonEtherealMatrix.xaml new file mode 100644 index 00000000..76f3023b --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonEtherealMatrix.xaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonForelimbs.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonForelimbs.xaml new file mode 100644 index 00000000..564d331a --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonForelimbs.xaml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonHellfireAegis.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonHellfireAegis.xaml new file mode 100644 index 00000000..98491821 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonHellfireAegis.xaml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonHindlegs.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonHindlegs.xaml new file mode 100644 index 00000000..adfa40e1 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonHindlegs.xaml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonInvisibleCore.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonInvisibleCore.xaml new file mode 100644 index 00000000..2963f2ff --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonInvisibleCore.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonSpiritBreath.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonSpiritBreath.xaml new file mode 100644 index 00000000..c77449a2 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonSpiritBreath.xaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonTail.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonTail.xaml new file mode 100644 index 00000000..48ce328b --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonTail.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonThorax.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonThorax.xaml new file mode 100644 index 00000000..40a09d86 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonThorax.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonTrueClaws.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonTrueClaws.xaml new file mode 100644 index 00000000..cb033272 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonTrueClaws.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonVigorousArmament.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonVigorousArmament.xaml new file mode 100644 index 00000000..9d9d002b --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonVigorousArmament.xaml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonVitalNexus.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonVitalNexus.xaml new file mode 100644 index 00000000..f73d628d --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonVitalNexus.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/AncientDragonWings.xaml b/MHFZ_Overlay/Assets/Icons/AncientDragonWings.xaml new file mode 100644 index 00000000..0275b483 --- /dev/null +++ b/MHFZ_Overlay/Assets/Icons/AncientDragonWings.xaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Assets/Icons/Armor_Sphere_Icon_White.xaml b/MHFZ_Overlay/Assets/Icons/Armor_Sphere_Icon_White.xaml index 60e4299e..078e9862 100644 --- a/MHFZ_Overlay/Assets/Icons/Armor_Sphere_Icon_White.xaml +++ b/MHFZ_Overlay/Assets/Icons/Armor_Sphere_Icon_White.xaml @@ -52,7 +52,7 @@ - + diff --git a/MHFZ_Overlay/Assets/Icons/Leg_Carve_Icon_White.xaml b/MHFZ_Overlay/Assets/Icons/Leg_Carve_Icon_White.xaml index 5b735e21..bfe7d349 100644 --- a/MHFZ_Overlay/Assets/Icons/Leg_Carve_Icon_White.xaml +++ b/MHFZ_Overlay/Assets/Icons/Leg_Carve_Icon_White.xaml @@ -69,72 +69,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_blue.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_blue.png new file mode 100644 index 00000000..dab1dde0 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_blue.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_cyan.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_cyan.png new file mode 100644 index 00000000..c7d12033 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_cyan.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_green.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_green.png new file mode 100644 index 00000000..c95d7cd0 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_green.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_max.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_max.png new file mode 100644 index 00000000..5172590a Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_max.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_orange.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_orange.png new file mode 100644 index 00000000..e68a406b Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_orange.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_purple.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_purple.png new file mode 100644 index 00000000..48a2bbae Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_purple.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_red.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_red.png new file mode 100644 index 00000000..3d990119 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_red.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/gauntlet_white.png b/MHFZ_Overlay/Assets/Icons/png/gauntlet_white.png new file mode 100644 index 00000000..cb4d977c Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/gauntlet_white.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/transcend_active.png b/MHFZ_Overlay/Assets/Icons/png/transcend_active.png new file mode 100644 index 00000000..3e5d7955 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/transcend_active.png differ diff --git a/MHFZ_Overlay/Assets/Icons/png/true_transcend.png b/MHFZ_Overlay/Assets/Icons/png/true_transcend.png new file mode 100644 index 00000000..be8e25d3 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/png/true_transcend.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo1.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo1.png new file mode 100644 index 00000000..77c7d829 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo1.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo10.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo10.png new file mode 100644 index 00000000..a6e1eb80 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo10.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo11.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo11.png new file mode 100644 index 00000000..90b1b46e Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo11.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo12.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo12.png new file mode 100644 index 00000000..5be7f898 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo12.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo13.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo13.png new file mode 100644 index 00000000..273e58e1 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo13.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo14.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo14.png new file mode 100644 index 00000000..d85f6417 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo14.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo15.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo15.png new file mode 100644 index 00000000..724c869a Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo15.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo2.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo2.png new file mode 100644 index 00000000..5b12ec3d Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo2.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo3.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo3.png new file mode 100644 index 00000000..d7cbaabc Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo3.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo4.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo4.png new file mode 100644 index 00000000..cff196f6 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo4.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo5.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo5.png new file mode 100644 index 00000000..5da9f9a5 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo5.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo6.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo6.png new file mode 100644 index 00000000..6cb34d68 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo6.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo7.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo7.png new file mode 100644 index 00000000..2faa210f Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo7.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo8.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo8.png new file mode 100644 index 00000000..b93beb5a Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo8.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/fumo9.png b/MHFZ_Overlay/Assets/Icons/touhou/fumo9.png new file mode 100644 index 00000000..543643f9 Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/fumo9.png differ diff --git a/MHFZ_Overlay/Assets/Icons/touhou/zenith_fumo.gif b/MHFZ_Overlay/Assets/Icons/touhou/zenith_fumo.gif new file mode 100644 index 00000000..84499f1b Binary files /dev/null and b/MHFZ_Overlay/Assets/Icons/touhou/zenith_fumo.gif differ diff --git a/MHFZ_Overlay/Assets/Sounds/challenge_start.wav b/MHFZ_Overlay/Assets/Sounds/challenge_start.wav new file mode 100644 index 00000000..14f0a442 Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/challenge_start.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/challenge_unlock.wav b/MHFZ_Overlay/Assets/Sounds/challenge_unlock.wav new file mode 100644 index 00000000..289d78e6 Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/challenge_unlock.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/gacha_rare.wav b/MHFZ_Overlay/Assets/Sounds/gacha_rare.wav new file mode 100644 index 00000000..15dba645 Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/gacha_rare.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/gacha_trial.wav b/MHFZ_Overlay/Assets/Sounds/gacha_trial.wav new file mode 100644 index 00000000..48d7a917 Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/gacha_trial.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/gacha_unlock.wav b/MHFZ_Overlay/Assets/Sounds/gacha_unlock.wav new file mode 100644 index 00000000..8e5e235a Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/gacha_unlock.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/hover.wav b/MHFZ_Overlay/Assets/Sounds/hover.wav new file mode 100644 index 00000000..3f8437ba Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/hover.wav differ diff --git a/MHFZ_Overlay/Assets/Sounds/select.wav b/MHFZ_Overlay/Assets/Sounds/select.wav new file mode 100644 index 00000000..136a2ccb Binary files /dev/null and b/MHFZ_Overlay/Assets/Sounds/select.wav differ diff --git a/MHFZ_Overlay/DataLoader.cs b/MHFZ_Overlay/DataLoader.cs index 91ce6f1e..c9eab450 100644 --- a/MHFZ_Overlay/DataLoader.cs +++ b/MHFZ_Overlay/DataLoader.cs @@ -415,26 +415,6 @@ public void CheckForIllegalModifications() /// public AddressModel Model { get; } // TODO: fix null warning - public string GetQuestTimeCompletion() - { - var totalQuestDuration = (double)this.Model.TimeDefInt() / Numbers.FramesPerSecond; // Total duration of the quest in seconds - var timeRemainingInQuest = (double)this.Model.TimeInt() / Numbers.FramesPerSecond; // Time left in the quest in seconds - - // Calculate the elapsed time by subtracting the time left from the total duration - var elapsedTime = totalQuestDuration - timeRemainingInQuest; - - // Convert the elapsed time from seconds to milliseconds - elapsedTime *= 1_000; - - // Convert the elapsed time to a TimeSpan object - var timeSpan = TimeSpan.FromMilliseconds(elapsedTime); - - // Format the TimeSpan object as a string - var formattedTime = timeSpan.ToString(TimeFormats.MinutesSecondsMilliseconds, CultureInfo.InvariantCulture); - - return formattedTime; - } - /// /// Creates the code cave. /// diff --git a/MHFZ_Overlay/MHFZ_Overlay.csproj b/MHFZ_Overlay/MHFZ_Overlay.csproj index 161fe7c7..d8b22ad2 100644 --- a/MHFZ_Overlay/MHFZ_Overlay.csproj +++ b/MHFZ_Overlay/MHFZ_Overlay.csproj @@ -20,7 +20,7 @@ mhfz-overlay Doriel Rivalet Doriel Rivalet - 0.29.1 + 0.30.0 https://github.com/DorielRivalet/mhfz-overlay https://github.com/DorielRivalet/mhfz-overlay.git git @@ -50,6 +50,7 @@ + @@ -58,6 +59,8 @@ + + @@ -245,6 +248,14 @@ + + + + + + + + @@ -718,14 +729,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -793,6 +847,27 @@ + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + PreserveNewest @@ -806,6 +881,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -830,6 +908,8 @@ PreserveNewest + + PreserveNewest @@ -995,7 +1075,13 @@ + + MSBuild:Compile + + + MSBuild:Compile + @@ -1003,8 +1089,14 @@ + + MSBuild:Compile + + + MSBuild:Compile + @@ -1015,34 +1107,67 @@ + + MSBuild:Compile + + + MSBuild:Compile + PreserveNewest + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + + + MSBuild:Compile + @@ -1133,6 +1258,14 @@ + + + + + + + + @@ -1606,6 +1739,8 @@ + + @@ -1616,14 +1751,71 @@ + + MSBuild:Compile + + + MSBuild:Compile + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + MSBuild:Compile + @@ -1639,16 +1831,17 @@ - + - + + - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -1662,7 +1855,8 @@ - + + diff --git a/MHFZ_Overlay/Models/Achievement.cs b/MHFZ_Overlay/Models/Achievement.cs index cb328cf2..e1d27971 100644 --- a/MHFZ_Overlay/Models/Achievement.cs +++ b/MHFZ_Overlay/Models/Achievement.cs @@ -6,10 +6,12 @@ namespace MHFZ_Overlay.Models; using System; using System.Collections.Generic; +using System.IO; using System.Windows; using System.Windows.Media; using MHFZ_Overlay.Models.Collections; using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; using MHFZ_Overlay.Views.Windows; using Wpf.Ui.Common; using Wpf.Ui.Controls; @@ -52,7 +54,9 @@ public void Show(Snackbar snackbar, Style style) Foreground = brushColor, }; snackbar.Appearance = ControlAppearance.Secondary; - MainWindow.MainWindowSoundPlayer?.Play(); + var s = (Settings)Application.Current.TryFindResource("Settings"); + var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\Sounds\victory.wav"); + AudioService.GetInstance().Play(fileName, MainWindow.MainWindowMediaPlayer, s.VolumeMain, s.VolumeAchievementUnlock); snackbar.Timeout = this.SnackbarTimeOut; snackbar.Show(); } diff --git a/MHFZ_Overlay/Models/Addresses/AddressModelHGE.cs b/MHFZ_Overlay/Models/Addresses/AddressModelHGE.cs index 53687ddf..05e32818 100644 --- a/MHFZ_Overlay/Models/Addresses/AddressModelHGE.cs +++ b/MHFZ_Overlay/Models/Addresses/AddressModelHGE.cs @@ -1745,4 +1745,7 @@ public AddressModelHGE(Mem m) /// public override int PartnyaBagItem10Qty() => this.M.Read2Byte("mhfo-hd.dll+E37D36E"); + + /// + public override int QuestToggleMonsterMode() => this.M.ReadByte("mhfo-hd.dll+E73D7B6"); } diff --git a/MHFZ_Overlay/Models/Addresses/AddressModelNotHGE.cs b/MHFZ_Overlay/Models/Addresses/AddressModelNotHGE.cs index 3774618b..012d4d4a 100644 --- a/MHFZ_Overlay/Models/Addresses/AddressModelNotHGE.cs +++ b/MHFZ_Overlay/Models/Addresses/AddressModelNotHGE.cs @@ -1664,4 +1664,6 @@ public AddressModelNotHGE(Mem m) /// public override int PartnyaBagItem10Qty() => this.M.Read2Byte("mhfo.dll+57457AE"); + + public override int QuestToggleMonsterMode() => this.M.ReadByte("mhfo.dll+5B05B8E"); } diff --git a/MHFZ_Overlay/Models/BezierCurve.cs b/MHFZ_Overlay/Models/BezierCurve.cs new file mode 100644 index 00000000..1e9514d2 --- /dev/null +++ b/MHFZ_Overlay/Models/BezierCurve.cs @@ -0,0 +1,46 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +/// +/// Cubic bezier curve with four control points. +/// +public class BezierCurve +{ + public Vector2 P0, P1, P2, P3; + + public BezierCurve(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3) + { + this.P0 = p0; + this.P1 = p1; + this.P2 = p2; + this.P3 = p3; + } + + public Vector2 Evaluate(float t) + { + float u = 1 - t; + float tt = t * t; + float uu = u * u; + float uuu = uu * u; + float ttt = tt * t; + + Vector2 B = new Vector2(); + B = uuu * P0; // (1-t) * (1-t) * (1-t) * P0 + B += 3 * uu * t * P1; // 3 * (1-t) * (1-t) * t * P1 + B += 3 * u * tt * P2; // 3 * (1-t) * t * t * P2 + B += ttt * P3; // t * t * t * P3 + + return B; + } +} + diff --git a/MHFZ_Overlay/Models/Bingo.cs b/MHFZ_Overlay/Models/Bingo.cs index 51aa3908..4be7adb2 100644 --- a/MHFZ_Overlay/Models/Bingo.cs +++ b/MHFZ_Overlay/Models/Bingo.cs @@ -11,23 +11,47 @@ namespace MHFZ_Overlay.Models; // TODO: ORM public sealed class Bingo { + /// + /// Primary key autoincrement. + /// public long BingoID { get; set; } public DateTime? CreatedAt { get; set; } public string? CreatedBy { get; set; } = string.Empty; + /// + /// The difficulty of the bingo. + /// public Difficulty Difficulty { get; set; } + /// + /// The list of run IDs related to the bingo run. + /// public List MonsterList { get; set; } = new List(); + /// + /// Unused. The weapon type used when bingo finished. + /// public string WeaponType { get; set; } = string.Empty; - public string Category { get; set; } = string.Empty; + /// + /// The category of the bingo. + /// + public BingoGauntletCategory Category { get; set; } + /// + /// The total amount of frames elapsed (sum of RunID, which is the MonsterList, Quests frames elapsed). + /// public long TotalFramesElapsed { get; set; } = long.MaxValue; + /// + /// The TotalFramesElapsed as a string format of HH:MM:SS.ff. + /// public string TotalTimeElapsed { get; set; } = string.Empty; + /// + /// The bingo score at bingo end. + /// public long Score { get; set; } } diff --git a/MHFZ_Overlay/Models/BingoCell.cs b/MHFZ_Overlay/Models/BingoCell.cs new file mode 100644 index 00000000..5dc5fc8d --- /dev/null +++ b/MHFZ_Overlay/Models/BingoCell.cs @@ -0,0 +1,55 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; + +/// +/// TODO +/// +public class BingoCell +{ + /// + /// The background color of the cell. Blue: Highest points line, Green: Completed, Yellow: Completed with 1 cart, Red: Completed with 2 or more carts. + /// + public string? BackgroundColor { get; set; } = CatppuccinMochaColors.NameHex["Crust"]; + + /// + /// Whether the cell is completed. Changes opacity via converter. + /// + public bool IsComplete { get; internal set; } + + /// + /// The bingo monster in the cell. + /// + public BingoMonster? Monster { get; internal set; } + + /// + /// TODO The number of carts in the bingo board. Used for decreasing scores in each cell. + /// + public int Carts { get; internal set; } + + /// + /// The weapon type bonuses in the bingo board. Used for increasing scores in each cell and for rerolls. + /// + public FrontierWeaponType WeaponTypeBonus { get; internal set; } + + /// + /// Whether the cell contains a book of secrets page. + /// + public bool ContainsBookOfSecretsPage { get; set; } + + /// + /// Whether the cell contains a random ancient dragon part's scrap. + /// + public bool ContainsAncientDragonPartScrap { get; set; } +} diff --git a/MHFZ_Overlay/Models/BingoMonster.cs b/MHFZ_Overlay/Models/BingoMonster.cs new file mode 100644 index 00000000..570d6b8e --- /dev/null +++ b/MHFZ_Overlay/Models/BingoMonster.cs @@ -0,0 +1,50 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MHFZ_Overlay.Models.Structures; + +public sealed class BingoMonster +{ + /// + /// The name of the monster + /// + public string Name { get; set; } = string.Empty; + + /// + /// The type of the monster. Used to determine the scraps type to give. + /// + public FrontierMonsterType Type { get; set; } + + /// + /// The image of the monster + /// + public string Image { get; set; } = string.Empty; + + /// + /// The list of quest IDs of the monster + /// + public List? QuestIDs { get; set; } + + /// + /// Whether the monster to hunt should be in Unlimited mode. If so, the QuestIDs are ignored if any and any non-custom quest counts as a completion. + /// + public bool IsUnlimited { get; set; } + + /// + /// The monster ID of the unlimited monster. + /// + public int UnlimitedMonsterID { get; set; } + + /// + /// The base bingo score obtained if defeated the monster. + /// + public int BaseScore { get; set; } +} diff --git a/MHFZ_Overlay/Models/BingoPlayer.cs b/MHFZ_Overlay/Models/BingoPlayer.cs new file mode 100644 index 00000000..cd543b4a --- /dev/null +++ b/MHFZ_Overlay/Models/BingoPlayer.cs @@ -0,0 +1,40 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; + +/// +/// TODO +/// +public class BingoPlayer +{ + /// + /// The amount of bingo points currently stored. + /// + public int BingoPoints { get; set; } + + /// + /// The amount of book of secrets pages currently stored. + /// + public int BookOfSecretsPages { get; set; } + + /// + /// The amount of unlocked book of secrets chapters. + /// + public List? UnlockedChapters { get; set; } + + /// + /// The amount of bingo shop upgrades unlocked. + /// + public List? UnlockedUpgrades { get; set; } +} diff --git a/MHFZ_Overlay/Models/BingoShopItem.cs b/MHFZ_Overlay/Models/BingoShopItem.cs new file mode 100644 index 00000000..c0de4fbd --- /dev/null +++ b/MHFZ_Overlay/Models/BingoShopItem.cs @@ -0,0 +1,28 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using MHFZ_Overlay.Models.Structures; + +/// +/// The bingo shop options. +/// +public sealed class BingoShopItem +{ + /// + /// The name of the option. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The cost of the option. + /// + public int Cost { get; set; } + + /// + /// Whether the option is unlocked. + /// + public bool IsUnlocked { get; set; } +} diff --git a/MHFZ_Overlay/Models/BingoUpgrade.cs b/MHFZ_Overlay/Models/BingoUpgrade.cs new file mode 100644 index 00000000..7ade320c --- /dev/null +++ b/MHFZ_Overlay/Models/BingoUpgrade.cs @@ -0,0 +1,48 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using MHFZ_Overlay.Models.Structures; + +/// +/// TODO readonlydictionary of bingoupgrades model +/// +public sealed class BingoUpgrade +{ + /// + /// The name of the upgrade. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the upgrade. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The icon of the upgrade. + /// + public string Icon { get; set; } = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png"; + + /// + /// The type of the bingo upgrade. + /// + public BingoUpgradeType Type { get; set; } + + /// + /// The max level possible of the upgrade. + /// + public int MaxLevel { get; set; } + + /// + /// The current level of the upgrade. + /// + public int CurrentLevel { get; set; } + + /// + /// Whether the upgrade is unlocked or not. + /// + public bool IsUnlocked { get; set; } +} diff --git a/MHFZ_Overlay/Models/Challenge.cs b/MHFZ_Overlay/Models/Challenge.cs index 9ad82432..8633f59c 100644 --- a/MHFZ_Overlay/Models/Challenge.cs +++ b/MHFZ_Overlay/Models/Challenge.cs @@ -3,10 +3,15 @@ // found in the LICENSE file. namespace MHFZ_Overlay.Models; + +using System; using System.Windows; +using System.Windows.Input; public sealed class Challenge { + public ICommand? StartChallengeCommand { get; set; } + /// /// Gets or sets the link to the banner image. /// @@ -56,4 +61,9 @@ public sealed class Challenge /// Gets or sets the name of the achievement id required. /// public string AchievementNameRequired { get; set; } = string.Empty; + + /// + /// The date when the challenge was unlocked + /// + public DateTime UnlockDate { get; set; } = DateTime.UnixEpoch; } diff --git a/MHFZ_Overlay/Models/ChallengeAncientDragon.cs b/MHFZ_Overlay/Models/ChallengeAncientDragon.cs new file mode 100644 index 00000000..0efb1364 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeAncientDragon.cs @@ -0,0 +1,39 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using Memory; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge ancient dragon. +/// +public sealed class ChallengeAncientDragon +{ + /// + /// The parts of the dragon. + /// + public List? Parts { get; set; } + + /// + /// The name of the dragon. + /// + public string Name { get; set; } = "Ancient Dragon"; + + /// + /// Whether the dragon is active or not. + /// + public bool IsActive { get; set; } +} diff --git a/MHFZ_Overlay/Models/ChallengeAncientDragonPart.cs b/MHFZ_Overlay/Models/ChallengeAncientDragonPart.cs new file mode 100644 index 00000000..405b5a71 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeAncientDragonPart.cs @@ -0,0 +1,83 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge ancient dragon part. +/// +public sealed class ChallengeAncientDragonPart +{ + /// + /// The name of the part. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the inactive part. + /// + public string InactivePartDescription { get; set; } = string.Empty; + + /// + /// The description of the active part. + /// + public string ActivePartDescription { get; set; } = string.Empty; + + /// + /// The link of the part image before a true transcend. + /// + public string InactivePartImageLink { get; set; } = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png"; + + /// + /// The link of the part image after a true transcend. + /// + public string ActivePartImageLink { get; set; } = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png"; + + /// + /// Whether the part boosts all other parts (except itself). + /// + public bool IsSource { get; set; } + + /// + /// The extra bonus when having the part that synergizes with this part. + /// + public ChallengeAncientDragonPart? NextSynergyPart { get; set; } + + /// + /// The description of the effect. + /// + public string Effect { get; set; } = string.Empty; + + /// + /// The description of the extra effect if having the Source part already. + /// + public string SourceEffect { get; set; } = string.Empty; + + /// + /// The description of the synergy effect if having the part that synergizes with this part. + /// + public string SynergyEffect { get; set; } = string.Empty; + + /// + /// Any additional details e.g. show how many bingo points you keep after a true transcend. + /// + public string Details { get; set; } = string.Empty; + + /// + /// The amount of gems required from monster types to make a scrap. Each part requires 100 scraps. + /// + public Dictionary? GemsRequiredForScrap { get; set; } +} diff --git a/MHFZ_Overlay/Models/ChallengeBookOfSecretsChapter.cs b/MHFZ_Overlay/Models/ChallengeBookOfSecretsChapter.cs new file mode 100644 index 00000000..a29d6bb6 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeBookOfSecretsChapter.cs @@ -0,0 +1,69 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using Memory; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge book of secrets upgrade. +/// +public sealed class ChallengeBookOfSecretsChapter +{ + /// + /// The name of the chapter. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the chapter. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The text shown after obtaining the chapter. + /// + public string Details { get; set; } = string.Empty; + + /// + /// The amount of pages required for the chapter. + /// + public int PagesRequired { get; set; } + + /// + /// Whether the chapter is unlocked. + /// + public bool IsUnlocked { get; set; } + + /// + /// The list of chapters this chapter unlocks. + /// + public List UnlockedChapters { get; set; } = new (); + + /// + /// The list of upgrades this chapter unlocks. + /// + public List UnlockedUpgrades { get; set; } = new(); + + /// + /// The required chapters to unlock this chapter. + /// + public List ChaptersRequired { get; set; } = new(); + + /// + /// The parts required for the chapter. + /// + public List ChallengeAncientDragonPartsRequired { get; set; } = new(); +} diff --git a/MHFZ_Overlay/Models/ChallengeItem.cs b/MHFZ_Overlay/Models/ChallengeItem.cs new file mode 100644 index 00000000..c83b5d9e --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeItem.cs @@ -0,0 +1,58 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge item. +/// +public sealed class ChallengeItem +{ + /// + /// The name of the item + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the item. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The link of the item image. + /// + public string ImageLink { get; set; } = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png"; + + /// + /// Whether the item can be stacked. + /// + public bool IsStackable { get; set; } + + /// + /// The max stack size in an inventory grid cell for the item. + /// + public int MaxStackSize { get; set; } + + /// + /// Whether there can be more than 1 of the item in the inventory. + /// + public bool IsUnique { get; set; } + + /// + /// The rarity of the item. From 1 to 12. + /// + public int Rarity { get; set; } +} diff --git a/MHFZ_Overlay/Models/ChallengeItemCraftingTable.cs b/MHFZ_Overlay/Models/ChallengeItemCraftingTable.cs new file mode 100644 index 00000000..aedd0d85 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeItemCraftingTable.cs @@ -0,0 +1,45 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge item crafting table. +/// +public sealed class ChallengeItemCraftingTable +{ + /// + /// The slots of the crafting table. + /// + public ChallengeItemSlot[,] Slots { get; set; } + + /// + /// The result of the craft. + /// + public ChallengeItem? Result { get; set; } + + public ChallengeItemCraftingTable(int size) + { + Slots = new ChallengeItemSlot[size, size]; + } + + public void UpdateResult() + { + // Determine what item can be crafted from the current arrangement of items in the Slots array + // and set the Result property to that item + // ... + } +} diff --git a/MHFZ_Overlay/Models/ChallengeItemInventory.cs b/MHFZ_Overlay/Models/ChallengeItemInventory.cs new file mode 100644 index 00000000..b2c342a9 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeItemInventory.cs @@ -0,0 +1,78 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge item inventory. +/// +public sealed class ChallengeItemInventory +{ + /// + /// The slots of the inventory + /// + public ChallengeItemSlot[,] Slots { get; private set; } + + public int CurrentWidth { get; private set; } + + public int CurrentHeight { get; private set; } + + public int MaxWidth { get; private set; } + + public int MaxHeight { get; private set; } + + public ChallengeItemInventory(int initialWidth, int initialHeight, int maxWidth, int maxHeight) + { + Slots = new ChallengeItemSlot[initialWidth, initialHeight]; + CurrentWidth = initialWidth; + CurrentHeight = initialHeight; + MaxWidth = maxWidth; + MaxHeight = maxHeight; + } + + /// + /// Upgrades the inventory size. + /// + /// + /// + public bool Upgrade(int newWidth, int newHeight) + { + if (newWidth > MaxWidth || newHeight > MaxHeight || newWidth <= CurrentWidth || newHeight <= CurrentHeight) + { + // Do not upgrade if the new dimensions are greater than the maximum dimensions or less than or equal to the current dimensions + return false; + } + + // Create a new array with the new dimensions + var newSlots = new ChallengeItemSlot[newWidth, newHeight]; + + // Copy the contents of the old array into the new array + for (int i = 0; i < CurrentWidth; i++) + { + for (int j = 0; j < CurrentHeight; j++) + { + newSlots[i, j] = Slots[i, j]; + } + } + + // Replace the old array with the new array + Slots = newSlots; + CurrentWidth = newWidth; + CurrentHeight = newHeight; + + return true; + } +} diff --git a/MHFZ_Overlay/Models/ChallengeItemSlot.cs b/MHFZ_Overlay/Models/ChallengeItemSlot.cs new file mode 100644 index 00000000..19d5fba4 --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeItemSlot.cs @@ -0,0 +1,27 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Octokit; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge item slot. +/// +public sealed class ChallengeItemSlot +{ + public ChallengeItemStack? ItemStack { get; set; } + public bool IsEmpty => ItemStack == null; +} diff --git a/MHFZ_Overlay/Models/ChallengeItemStack.cs b/MHFZ_Overlay/Models/ChallengeItemStack.cs new file mode 100644 index 00000000..86a272aa --- /dev/null +++ b/MHFZ_Overlay/Models/ChallengeItemStack.cs @@ -0,0 +1,26 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows; +using System.Windows.Media; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; + +/// +/// The challenge item stack. +/// +public sealed class ChallengeItemStack +{ + public ChallengeItem? Item { get; set; } + public int Quantity { get; set; } +} diff --git a/MHFZ_Overlay/Models/Collections/AchievementRankPoints.cs b/MHFZ_Overlay/Models/Collections/AchievementRankPoints.cs new file mode 100644 index 00000000..6b08e876 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/AchievementRankPoints.cs @@ -0,0 +1,36 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The base points depending on achievement rank. +/// +public static class AchievementRankPoints +{ + public static ReadOnlyDictionary RankPoints { get; } = new(new Dictionary + { + { + AchievementRank.None, 0 + }, + { + AchievementRank.Bronze, 5 + }, + { + AchievementRank.Silver, 10 + }, + { + AchievementRank.Gold, 15 + }, + { + AchievementRank.Platinum, 30 + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/Achievements.cs b/MHFZ_Overlay/Models/Collections/Achievements.cs index 34379ad8..96180deb 100644 --- a/MHFZ_Overlay/Models/Collections/Achievements.cs +++ b/MHFZ_Overlay/Models/Collections/Achievements.cs @@ -9,6 +9,7 @@ namespace MHFZ_Overlay.Models.Collections; using System.Collections.ObjectModel; using MHFZ_Overlay.Models; using MHFZ_Overlay.Models.Structures; +using Octokit; /// /// Achievements dictionary. TODO. @@ -2614,7 +2615,7 @@ public static class Achievements 199, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "A lonely and starving wolf", + Title = "A Lonely and Starving Wolf", Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/monster_blue2.jpg", @@ -2770,7 +2771,7 @@ public static class Achievements 211, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "Oh noes! My sunglasses!", + Title = "Oh Noes! My Sunglasses!", Description = string.Empty, Rank = AchievementRank.Bronze, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/monster_red2.jpg", @@ -2839,7 +2840,7 @@ public static class Achievements Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/medal_stamp.jpg", - Objective = "Obtain S Rank in all single-player MezFes minigames", + Objective = "Obtain S Rank in Nyanrendo, Dokkan Battle Cats, Guuku Scoop and Panic Honey.", IsSecret = true, Hint = "Do you like minigames?", } @@ -2930,7 +2931,7 @@ public static class Achievements Description = string.Empty, Rank = AchievementRank.Bronze, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/duremudira.jpg", - Objective = "Defeat 2nd District duremudira 100 times.", + Objective = "Defeat 2nd District Duremudira 25 times.", IsSecret = true, Hint = "You killed that many monsters?!", } @@ -2952,11 +2953,11 @@ public static class Achievements 225, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "Fumo", + Title = "Fumo #1", Description = string.Empty, Rank = AchievementRank.Bronze, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", - Objective = "Click a Fumo.", + Objective = "Click a Fumo in the configuration window.", IsSecret = true, Hint = "Fumo.", } @@ -4551,7 +4552,7 @@ public static class Achievements 348, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "20% more damage for 99% more effort", + Title = "20% More Damage for 99% More Effort", Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/berserk_raviente.jpg", @@ -4659,7 +4660,7 @@ public static class Achievements Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", - Objective = "Obtain 1000 Bingo points or more in 1 bingo card completion.", + Objective = "Complete a bingo run without fainting and with 4 lines being crossed at once.", IsSecret = false, Hint = string.Empty, } @@ -4776,7 +4777,7 @@ public static class Achievements Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/zenny.jpg", - Objective = "Generate 10000 gacha pulls.", + Objective = "Generate 10,000 gacha pulls.", IsSecret = false, Hint = string.Empty, } @@ -4902,7 +4903,7 @@ public static class Achievements 375, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "In search of a new frontier", + Title = "In Search of a New Frontier", Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", @@ -5071,7 +5072,7 @@ public static class Achievements 388, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "One star to rule them all", + Title = "One Star to Rule Them All", Description = string.Empty, Rank = AchievementRank.Gold, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", @@ -5084,7 +5085,7 @@ public static class Achievements 389, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "Two stars make a supernova", + Title = "Two Stars Make a Supernova", Description = string.Empty, Rank = AchievementRank.Gold, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", @@ -5123,7 +5124,7 @@ public static class Achievements 392, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "A 5-Star collection", + Title = "A 5-Star Collection", Description = string.Empty, Rank = AchievementRank.Gold, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", @@ -5201,7 +5202,7 @@ public static class Achievements 398, new Achievement() { CompletionDate = DateTime.UnixEpoch, - Title = "Gacha up to eleven", + Title = "Gacha Up To Eleven", Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", @@ -5308,7 +5309,7 @@ that beautiful bird regains its brilliance Description = string.Empty, Rank = AchievementRank.Platinum, Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/extreme_poison.png", - Objective = "Defeat Zenith★4 Gasurabazura solo without Poison Cure.", + Objective = "Defeat Zenith★4 Gasurabazura solo without Anti-Venom.", IsSecret = true, Hint = "No cures allowed! But your halk is allowed to help.", } @@ -5326,5 +5327,484 @@ that beautiful bird regains its brilliance Hint = string.Empty, } }, + { + 407, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "UNKNOWN Was Her?", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Defeat Upper Shiten Unknown solo without any items.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 408, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Challenge Accepted", + Description = string.Empty, + Rank = AchievementRank.Silver, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/stamp.jpg", + Objective = "Accept a rare gacha challenge.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 409, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "No Cheats Allowed!", + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Enter the Konami Code on the start of a quest.", + IsSecret = true, + Hint = "Sorry, but that cheat code won't give you 30 more tries on this quest.", + } + }, + { + 410, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Blinky", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete 1 Blinking Nargacuga True Slay quest with a certain Switch Axe F.", + IsSecret = true, + Hint = @"You found a book and a weapon. + +The book reads: +""For those who are new to this environment, perfection is not something you achieve but something all must eventually fall into the ranks to. + +It is a forced compulsion to be better, and that is what's enjoyable about entering this community. + +In time, I will steal everything there is to need in order to trivialise content as people have done. + +Well, the alternative is to believe you're a failure who will never survive in this game which I am not pessimistic about. + +So yuh, it will be worthwhile because inevitably you will be forced to be stronger, even if you have to beat up your body just so that it cooperates. + +Well, no merits are earned yet to justify a personality. So in the meantime, being humble and full of self-doubt is correct. At least until you proved your place in this game as, not being a failure. + +The only question is how long it'll take to be competent, hmmm."" + +You turn the pages... + +""I feel like garbage right now, because I haven't done anything. But instead of looking up at those people and being awe-inspired about how they're better than me, I seek to take that for myself. Because that's how you survive in this community, it is a compulsory progression, otherwise you can screw off and die somewhere. + +I have the utmost gratitude for people who are strong, but it's also the source of dissatisfaction that I will settle for nothing less than to take that for myself. + +Otherwise, crash and burn if I'm not good enough. +I'm sure everyone will do great in the end o wo + +As long as they don't quit. + +That's why we came here, right? We wouldn't find this place if we err, sucked at MH."" + +Having read the inspiring notes, you decide to equip the Black Savage Charaxt and embark on a quest. + +During the travel to your destination, the top of the Great Forest, you notice that the weapon seems to have been made before Zeniths were known.", + } + }, + { + 411, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "A Very Stylish Hunter", + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/hunter.jpg", + Objective = "Complete a quest with Stylish Assault Up and Stylish Up.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 412, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "The Rarest Guild Card", + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Hunt 100 Ashen Lao-Shan Lung.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 413, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Apparently Immortal Monsters", + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Hunt 1 Ashen Lao-Shan Lung, HR3 Yama Tsukami and HR3 Shen Gaoren.", + IsSecret = true, + Hint = "These monsters only die when they want to.", + } + }, + { + 414, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Obtaining All the Buffs", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete 1 Arrogant Duremudira True Slay quest with Secret Technique.", + IsSecret = true, + Hint = "Who said it's only useful for the knife attack?", + } + }, + { + 415, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "The Bingo Gauntlet", + Description = string.Empty, + Rank = AchievementRank.Gold, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_max.png", + Objective = "Start a bingo run with all gauntlet boosts active.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 416, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Master of Bingo", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/zenny.jpg", + Objective = "Buy all Bingo challenge upgrades.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 417, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "The Sky's the Limit", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/zenny.jpg", + Objective = "Buy all Sky Corridor challenge upgrades.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 418, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "No more upgrades!", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/zenny.jpg", + Objective = "Buy all Gacha challenge upgrades.", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 419, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #2", // Patchouli + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in Bingo challenge.", + IsSecret = true, + Hint = +@"[] [] [] [] [S] +[] [] [] [O] [] +[] [] [M] [] [] +[] [U] [] [] [] +[F] [] [] [] []", + } + }, + { + 420, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #3", // Flande + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in Gacha challenge. Or not, it's probably a scam.", + IsSecret = true, + Hint = "Fumo is very expensive.", + } + }, + { + 421, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #4", // Remilia + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in Sky Corridor challenge. Looks like it underestimated your power.", + IsSecret = true, + Hint = "Fumo says it's over, it has the high ground.", + } + }, + { + 422, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #5", // Yuyuko + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in Frontier Compendium website and follow the hints.", + IsSecret = true, + Hint = "Get a hint from a fumo, not here!", + } + }, + { + 423, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "New Game+", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Fully transcend once in a Bingo challenge.", + IsSecret = true, + Hint = "There's more to bingo than meets the eye.", + } + }, + { + 424, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "The Infinity Gauntlet", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Use the ancient gauntlet to get an infinite amount of points in Sky Corridor. Or at least, try to.", + IsSecret = true, + Hint = "There's not one, but multiple gauntlets.", + } + }, + { + 425, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "No More Heroes in Ruins", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete Tome I of the Book of Secrets (Challenge).", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 426, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Dubito, Ergo Cogito, Ergo Sum", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete Tome II of the Book of Secrets (Challenge).", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 427, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Friend or Foe", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete Tome III of the Book of Secrets (Challenge).", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 428, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Shattered Dimensions", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Complete Tome IV of the Book of Secrets (Challenge).", + IsSecret = false, + Hint = string.Empty, + } + }, + { + 429, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #6", // Tenshi + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in a chest.", + IsSecret = true, + Hint = "These chests can hold many hidden treasures.", + } + }, + { + 430, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #7", // Inaba + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo in a box.", + IsSecret = true, + Hint = "These boxes can have more than just ammo.", + } + }, + { + 431, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #8", // Suika + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo near a monster.", + IsSecret = true, + Hint = "This fumo reminds me of a certain fanged beast.", + } + }, + { + 432, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #9", // Youmu + Description = string.Empty, + Rank = AchievementRank.Bronze, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_red.jpg", + Objective = "Click a fumo inside a wooden chest.", + IsSecret = true, + Hint = "Why was it hiding inside a wooden chest?", + } + }, + { + 433, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #10", // Cirno + Description = string.Empty, + Rank = AchievementRank.Silver, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_white.jpg", + Objective = "Click a fumo in the starry sky.", + IsSecret = true, + Hint = "You saw a comet and asked for a wish.", + } + }, + { + 434, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #11", // Sakuya + Description = string.Empty, + Rank = AchievementRank.Silver, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_white.jpg", + Objective = "Click a fumo near a knife.", + IsSecret = true, + Hint = "I wonder why we use transcend burst with the carving knife.", + } + }, + { + 435, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #12", // Suwako + Description = string.Empty, + Rank = AchievementRank.Silver, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_white.jpg", + Objective = "Click a fumo near a frog.", + IsSecret = true, + Hint = "This frog was hiding a secret!", + } + }, + { + 436, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #13", // Koishi + Description = string.Empty, + Rank = AchievementRank.Gold, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", + Objective = "Click a fumo near a monster's eye.", + IsSecret = true, + Hint = "We might find something if we go where these eyes are pointing.", + } + }, + { + 437, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #14", // Reimu + Description = string.Empty, + Rank = AchievementRank.Gold, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", + Objective = "Click a fumo in Frontier Compendium website and follow the hints.", + IsSecret = true, + Hint = "Get a hint from a fumo, not here!", + } + }, + { + 438, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Fumo #15", // Marisa + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Click a fumo in Sky Corridor Headquarters Entrance.", + IsSecret = true, + Hint = "Get a hint from a fumo, not here!", + } + }, + { + 439, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Back to the Land of Illusions", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Help Marisa go back to her universe.", + IsSecret = true, + Hint = "Hopefully we don't have any more dimensional shenanigans.", + } + }, + { + 440, new Achievement() + { + CompletionDate = DateTime.UnixEpoch, + Title = "Unlimited Power", + Description = string.Empty, + Rank = AchievementRank.Platinum, + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_blue.jpg", + Objective = "Have the power to beat 100 Unlimited monsters.", + IsSecret = true, + Hint = "POWER!", + } + }, }); } diff --git a/MHFZ_Overlay/Models/Collections/BingoDifficultyCarts.cs b/MHFZ_Overlay/Models/Collections/BingoDifficultyCarts.cs new file mode 100644 index 00000000..bab53de0 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/BingoDifficultyCarts.cs @@ -0,0 +1,33 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The bingo difficulty carts base. +/// +public static class BingoDifficultyCarts +{ + public static ReadOnlyDictionary DifficultyCarts { get; } = new(new Dictionary + { + { + Difficulty.Easy, 7 + }, + { + Difficulty.Medium, 5 + }, + { + Difficulty.Hard, 3 + }, + { + Difficulty.Extreme, 0 + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/BingoMonsters.cs b/MHFZ_Overlay/Models/Collections/BingoMonsters.cs index 3b5870a6..3fc07eee 100644 --- a/MHFZ_Overlay/Models/Collections/BingoMonsters.cs +++ b/MHFZ_Overlay/Models/Collections/BingoMonsters.cs @@ -6,175 +6,1338 @@ namespace MHFZ_Overlay.Models.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; /// /// The bingo monster difficulty list. /// public static class BingoMonsters { - public static ReadOnlyDictionary> BingoMonsterDifficulty { get; } = new (new Dictionary> + public static ReadOnlyDictionary> DifficultyBingoMonster { get; } = new (new Dictionary> { - // TODO make a function with input monster name and output dictionary monsterID rankbandID - // TODO i should use questid instead and use monster names as comments // Extreme difficulty is same as hard difficulty but the bingo board is twice as big. + // Custom quests and event quests for monsters that aren't event-only are not counted. { - Difficulty.Easy, new List + Difficulty.Easy, new List { - "Elzelion", - "Zenaserisu", - "Zinogre", - "Seregios", - "Nargacuga", - "Uragaan", - "Shagaru Magala", - "Gore Magala", - "Amatsu", - "Mi Ru", - "Barioth", - "Brachydios", - "Deviljho", - "Diorex", - "Garuba Daora", - "Varusaburosu", - "Gureadomosu", - "Zerureusu", - "Meraginasu", - "Thirsty Pariapuria", - "Voljang", - "Stygian Zinogre", - "Guanzorumu", - "Ruling Guanzorumu", - "Keoaruboru", - "Rukodiora", - "Rebidiora", - "Lv1000 Shantien", - "Lv1000 Fatalis", - "Lv1000 Crimson Fatalis", - "Lv1000 Disufiroa", - "Lower Shiten Disufiroa", - "Lower Shiten Unknown", - "Toa Tesukatora", - "Yama Kurai", - "Zenith★1 Espinas", - "Zenith★1 Hypnoc", - "Zenith★1 Khezu", - "Zenith★1 Daimyo Hermitaur", - "Zenith★1 Inagami", - "Zenith★1 Anorupatisu", - "Zenith★1 Toridcless", - "Zenith★1 Plesioth", - "Zenith★1 Rukodiora", - "Zenith★1 Rathalos", - "Zenith★1 Giaorugu", - "Zenith★1 Blangonga", - "Zenith★1 Akura Vashimu", - "Zenith★1 Tigrex", - "Zenith★1 Hyujikiki", - "Zenith★1 Midogaron", - "Zenith★1 Gasurabazura", - "Zenith★1 Taikun Zamuza", - "Zenith★1 Harudomerugu", - "Zenith★1 Baruragaru", - "Zenith★1 Gravios", - "Zenith★1 Doragyurosu", - "Zenith★1 Bogabadorumu", - "Zenith★2 Espinas", - "Zenith★2 Hypnoc", - "Zenith★2 Khezu", - "Zenith★2 Daimyo Hermitaur", - "Zenith★2 Inagami", - "Zenith★2 Anorupatisu", - "Zenith★2 Toridcless", - "Zenith★2 Plesioth", - "Zenith★2 Rukodiora", - "Zenith★2 Rathalos", - "Zenith★2 Giaorugu", - "Zenith★2 Blangonga", - "Zenith★2 Akura Vashimu", - "Zenith★2 Tigrex", - "Zenith★2 Hyujikiki", - "Zenith★2 Midogaron", - "Zenith★2 Gasurabazura", - "Zenith★2 Taikun Zamuza", - "Zenith★2 Harudomerugu", - "Zenith★2 Baruragaru", - "Zenith★2 Gravios", - "Zenith★2 Doragyurosu", - "Zenith★2 Bogabadorumu", + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/elzelion.png", + Name = "Elzelion", + QuestIDs = new List { 23626, }, + BaseScore = 10, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zinogre.png", + Name = "Zinogre", + QuestIDs = new List { 23499, }, + BaseScore = 8, + Type = FrontierMonsterType.FangedWyvern, + }, // zin + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/seregios.png", + Name = "Seregios", + QuestIDs = new List { 23667, }, + BaseScore = 9, + Type = FrontierMonsterType.FlyingWyvern, + }, // seregios + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/nargacuga.png", + Name = "Nargacuga", + QuestIDs = new List { 23494, }, + BaseScore = 5, + Type = FrontierMonsterType.FlyingWyvern, + }, // narga + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/uragaan.png", + Name = "Uragaan", + QuestIDs = new List { 23495, }, + BaseScore = 6, + Type = FrontierMonsterType.BruteWyvern, + }, // uragaan + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shagaru_magala.png", + Name = "Shagaru Magala", + QuestIDs = new List { 23528, }, + BaseScore = 9, + Type = FrontierMonsterType.ElderDragon, + }, // shagaru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gore_magala.png", + Name = "Gore Magala", + QuestIDs = new List { 23513, }, + BaseScore = 5, + Type = FrontierMonsterType.Other, + }, // gore + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/amatsu.png", + Name = "Amatsu", + QuestIDs = new List { 23643, }, + BaseScore = 6, + Type = FrontierMonsterType.ElderDragon, + }, // amatsu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/mi_ru.png", + Name = "Mi Ru", + QuestIDs = new List { 54244, }, + BaseScore = 5, + Type = FrontierMonsterType.Other, + }, // mi ru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/barioth.png", + Name = "Barioth", + QuestIDs = new List { 23496, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // barioth + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/brachydios.png", + Name = "Brachydios", + QuestIDs = new List { 23497, }, + BaseScore = 6, + Type = FrontierMonsterType.BruteWyvern, + }, // brachy + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/deviljho.png", + Name = "Deviljho", + QuestIDs = new List { 23498, }, + BaseScore = 5, + Type = FrontierMonsterType.BruteWyvern, + }, // jho + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/diorex.png", + Name = "Diorex", + QuestIDs = new List { 23490, }, + BaseScore = 1, + Type = FrontierMonsterType.FlyingWyvern, + }, // diorex + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/garuba_daora.png", + Name = "Garuba Daora", + QuestIDs = new List { 23489, }, + BaseScore = 8, + Type = FrontierMonsterType.ElderDragon, + }, // garuba + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/varusaburosu.png", + Name = "Varusaburosu", + QuestIDs = new List { 23488, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // varusa + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gureadomosu.png", + Name = "Gureadomosu", + QuestIDs = new List { 23487, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // gurea + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/toa_tesukatora.png", + Name = "Toa Tesukatora", + QuestIDs = new List { 23485, }, + BaseScore = 9, + Type = FrontierMonsterType.ElderDragon, + }, // toa + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/yama_kurai.png", + Name = "Yama Kurai", + QuestIDs = new List { 23486, }, + BaseScore = 10, + Type = FrontierMonsterType.ElderDragon, + }, // kurai + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zerureusu.png", + Name = "Zerureusu", + QuestIDs = new List { 23492, }, + BaseScore = 4, + Type = FrontierMonsterType.FlyingWyvern, + }, // zeru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/meraginasu.png", + Name = "Meraginasu", + QuestIDs = new List { 23491, }, + BaseScore = 5, + Type = FrontierMonsterType.FlyingWyvern, + }, // mera + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/voljang.png", + Name = "Voljang", + QuestIDs = new List { 23484, }, + BaseScore = 8, + Type = FrontierMonsterType.FangedBeast, + }, // voljang + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/stygian_zinogre.png", + Name = "Stygian Zinogre", + QuestIDs = new List { 23493, }, + BaseScore = 9, + Type = FrontierMonsterType.FangedWyvern, + }, // stygian + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/thirsty_pariapuria.png", + Name = "Thirsty Pariapuria", + QuestIDs = new List { Numbers.QuestIDThirstyPariapuria, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/ruling_guanzorumu.png", + Name = "Ruling Guanzorumu", + QuestIDs = new List { Numbers.QuestIDRulingGuanzorumu, }, + BaseScore = 12, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/keoaruboru.png", + Name = "Keoaruboru", + QuestIDs = new List { 58043, }, + BaseScore = 3, + Type = FrontierMonsterType.ElderDragon, + }, // keo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/guanzorumu.png", + Name = "Guanzorumu", + QuestIDs = new List { 23424, }, + BaseScore = 6, + Type = FrontierMonsterType.ElderDragon, + }, // guanzo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_shantien.png", + Name = "Lv1000 Shantien", + QuestIDs = new List { 23587, }, + BaseScore = 12, + Type = FrontierMonsterType.ElderDragon, + }, // lv1000 shantien + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/disufiroa.png", + Name = "Lv1000 Disufiroa", + QuestIDs = new List { 23591, }, + BaseScore = 13, + Type = FrontierMonsterType.ElderDragon, + }, // lv1000 disu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_fatalis.png", + Name = "Lv1000 Fatalis", + QuestIDs = new List { 23595, }, + BaseScore = 11, + Type = FrontierMonsterType.ElderDragon, + }, // lv1000 fatalis + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_crimson_fatalis.png", + Name = "Lv1000 Crimson Fatalis", + QuestIDs = new List { 23599, }, + BaseScore = 12, + Type = FrontierMonsterType.ElderDragon, + }, // lv1000 crimson + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shiten_disufiroa.png", + Name = "Lower Shiten Disufiroa", + QuestIDs = new List { 23602, }, + BaseScore = 15, + Type = FrontierMonsterType.ElderDragon, + }, // lower shiten disu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shiten_unknown.png", + Name = "Lower Shiten Unknown", + QuestIDs = new List { 23604, }, + BaseScore = 10, + Type = FrontierMonsterType.FlyingWyvern, + }, // lower shiten unknown + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_akura_vashimu.gif", + Name = "Zenith★1 Akura Vashimu", + QuestIDs = new List { 23536, }, + BaseScore = 7, + Type = FrontierMonsterType.Carapaceon, + }, // z1+2 akura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_akura_vashimu.gif", + Name = "Zenith★2 Akura Vashimu", + QuestIDs = new List { 23537, }, + BaseScore = 14, + Type = FrontierMonsterType.Carapaceon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_anorupatisu.gif", + Name = "Zenith★1 Anorupatisu", + QuestIDs = new List { 23718, }, + BaseScore = 9, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1+2 anoru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_anorupatisu.gif", + Name = "Zenith★2 Anorupatisu", + QuestIDs = new List { 23719, }, + BaseScore = 18, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_inagami.gif", + Name = "Zenith★1 Inagami", + QuestIDs = new List { 23644, }, + BaseScore = 10, + Type = FrontierMonsterType.ElderDragon, + }, // z1 inagami + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_inagami.gif", + Name = "Zenith★2 Inagami", + QuestIDs = new List { 23645, }, + BaseScore = 20, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_espinas.gif", + Name = "Zenith★1 Espinas", + QuestIDs = new List { 23480, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 espi + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_espinas.gif", + Name = "Zenith★2 Espinas", + QuestIDs = new List { 23481, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gasurabazura.gif", + Name = "Zenith★1 Gasurabazura", + QuestIDs = new List { 23668, }, + BaseScore = 9, + Type = FrontierMonsterType.BruteWyvern, + }, // z1 gasura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gasurabazura.gif", + Name = "Zenith★2 Gasurabazura", + QuestIDs = new List { 23669, }, + BaseScore = 18, + Type = FrontierMonsterType.BruteWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_plesioth.gif", + Name = "Zenith★1 Plesioth", + QuestIDs = new List { 23622, }, + BaseScore = 8, + Type = FrontierMonsterType.PiscineWyvern, + }, // z1 plesioth + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_plesioth.gif", + Name = "Zenith★2 Plesioth", + QuestIDs = new List { 23623, }, + BaseScore = 16, + Type = FrontierMonsterType.PiscineWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_giaorugu.gif", + Name = "Zenith★1 Giaorugu", + QuestIDs = new List { 23610, }, + BaseScore = 7, + Type = FrontierMonsterType.BruteWyvern, + }, // z1 giao + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_giaorugu.gif", + Name = "Zenith★2 Giaorugu", + QuestIDs = new List { 23611, }, + BaseScore = 14, + Type = FrontierMonsterType.BruteWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gravios.gif", + Name = "Zenith★1 Gravios", + QuestIDs = new List { 23709, }, + BaseScore = 9, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 gravios + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gravios.gif", + Name = "Zenith★2 Gravios", + QuestIDs = new List { 23710, }, + BaseScore = 18, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_daimyo_hermitaur.gif", + Name = "Zenith★1 Daimyo Hermitaur", + QuestIDs = new List { 23476, }, + BaseScore = 6, + Type = FrontierMonsterType.Carapaceon, + }, // z1 daimyo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_daimyo_hermitaur.gif", + Name = "Zenith★2 Daimyo Hermitaur", + QuestIDs = new List { 23477, }, + BaseScore = 12, + Type = FrontierMonsterType.Carapaceon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_tigrex.gif", + Name = "Zenith★1 Tigrex", + QuestIDs = new List { 23540, }, + BaseScore = 5, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 tigrex + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_tigrex.gif", + Name = "Zenith★2 Tigrex", + QuestIDs = new List { 23541, }, + BaseScore = 10, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_blangonga.gif", + Name = "Zenith★1 Blangonga", + QuestIDs = new List { 23516, }, + BaseScore = 6, + Type = FrontierMonsterType.FangedBeast, + }, // z1 blango + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_blangonga.gif", + Name = "Zenith★2 Blangonga", + QuestIDs = new List { 23517, }, + BaseScore = 12, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_doragyurosu.gif", + Name = "Zenith★1 Doragyurosu", + QuestIDs = new List { 23659, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 dora + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_doragyurosu.gif", + Name = "Zenith★2 Doragyurosu", + QuestIDs = new List { 23660, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_toridcless.gif", + Name = "Zenith★1 Toridcless", + QuestIDs = new List { 23655, }, + BaseScore = 6, + Type = FrontierMonsterType.BirdWyvern, + }, // z1 torid + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_toricless.gif", + Name = "Zenith★2 Toricless", + QuestIDs = new List { 23656, }, + BaseScore = 12, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_baruragaru.gif", + Name = "Zenith★1 Baruragaru", + QuestIDs = new List { 23713, }, + BaseScore = 9, + Type = FrontierMonsterType.Leviathan, + }, // z1 baru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_baruragaru.gif", + Name = "Zenith★2 Baruragaru", + QuestIDs = new List { 23714, }, + BaseScore = 18, + Type = FrontierMonsterType.Leviathan, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hypnoc.gif", + Name = "Zenith★1 Hypnocatrice", + QuestIDs = new List { 23468, }, + BaseScore = 7, + Type = FrontierMonsterType.BirdWyvern, + }, // z1 hypnoc + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hypnoc.gif", + Name = "Zenith★2 Hypnocatrice", + QuestIDs = new List { 23469, }, + BaseScore = 14, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hyujikiki.gif", + Name = "Zenith★1 Hyujikiki", + QuestIDs = new List { 23606, }, + BaseScore = 8, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 hyuji + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hyujikiki.gif", + Name = "Zenith★2 Hyujikiki", + QuestIDs = new List { 23607, }, + BaseScore = 16, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_khezu.gif", + Name = "Zenith★1 Khezu", + QuestIDs = new List { 23472, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 khezu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_khezu.gif", + Name = "Zenith★2 Khezu", + QuestIDs = new List { 23473, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_bogabadorumu.gif", + Name = "Zenith★1 Bogabadorumu", + QuestIDs = new List { 23705, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 boggy + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_bogabadorumu.gif", + Name = "Zenith★2 Bogabadorumu", + QuestIDs = new List { 23706, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_midogaron.gif", + Name = "Zenith★1 Midogaron", + QuestIDs = new List { 23614, }, + BaseScore = 7, + Type = FrontierMonsterType.FangedBeast, + }, // z1 mido + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_midogaron.gif", + Name = "Zenith★2 Midogaron", + QuestIDs = new List { 23615, }, + BaseScore = 14, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rathalos.gif", + Name = "Zenith★1 Rathalos", + QuestIDs = new List { 23520, }, + BaseScore = 6, + Type = FrontierMonsterType.FlyingWyvern, + }, // z1 rath + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rathalos.gif", + Name = "Zenith★2 Rathalos", + QuestIDs = new List { 23521, }, + BaseScore = 12, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rukodiora.gif", + Name = "Zenith★1 Rukodiora", + QuestIDs = new List { 23618, }, + BaseScore = 9, + Type = FrontierMonsterType.ElderDragon, + }, // z1 ruko + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rukodiora.gif", + Name = "Zenith★2 Rukodiora", + QuestIDs = new List { 23619, }, + BaseScore = 18, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_taikun_zamuza.gif", + Name = "Zenith★1 Taikun Zamuza", + QuestIDs = new List { 55923, }, + BaseScore = 9, + Type = FrontierMonsterType.Carapaceon, + }, // z1 taikun + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_taikun_zamuza.gif", + Name = "Zenith★2 Taikun Zamuza", + QuestIDs = new List { 55924, }, + BaseScore = 18, + Type = FrontierMonsterType.Carapaceon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_harudomerugu.gif", + Name = "Zenith★1 Harudomerugu", + QuestIDs = new List { 55929, }, + BaseScore = 6, + Type = FrontierMonsterType.ElderDragon, + }, // z1 harudo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_harudomerugu.gif", + Name = "Zenith★2 Harudomerugu", + QuestIDs = new List { 55930, }, + BaseScore = 12, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/duremudira.png", + Name = "1st District Duremudira", + QuestIDs = new List { Numbers.QuestIDFirstDistrictDuremudira, }, + BaseScore = 15, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/yian_kut-ku.png", + Name = "UL Yian Kut-Ku", + BaseScore = 8, + IsUnlimited = true, + UnlimitedMonsterID = 6, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/lavasioth.png", + Name = "UL Lavasioth", + BaseScore = 9, + IsUnlimited = true, + UnlimitedMonsterID = 75, + Type = FrontierMonsterType.PiscineWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gypceros.png", + Name = "UL Gypceros", + BaseScore = 9, + IsUnlimited = true, + UnlimitedMonsterID = 20, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gougarf.png", + Name = "UL Gougarf", + BaseScore = 10, + IsUnlimited = true, + UnlimitedMonsterID = 123, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gogomoa.png", + Name = "UL Gogomoa", + BaseScore = 9, + IsUnlimited = true, + UnlimitedMonsterID = 101, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shogun_ceanataur.png", + Name = "UL Shogun Ceanataur", + BaseScore = 9, + IsUnlimited = true, + UnlimitedMonsterID = 67, + Type = FrontierMonsterType.Carapaceon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/dyuragaua.png", + Name = "UL Dyuragaua", + BaseScore = 7, + IsUnlimited = true, + UnlimitedMonsterID = 94, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/cephadrome.png", + Name = "UL Cephadrome", + BaseScore = 7, + IsUnlimited = true, + UnlimitedMonsterID = 8, + Type = FrontierMonsterType.PiscineWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/bulldrome.png", + Name = "UL Bulldrome", + BaseScore = 6, + IsUnlimited = true, + UnlimitedMonsterID = 68, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/basarios.png", + Name = "UL Basarios", + BaseScore = 9, + IsUnlimited = true, + UnlimitedMonsterID = 22, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/congalala.png", + Name = "UL Congalala", + BaseScore = 8, + IsUnlimited = true, + UnlimitedMonsterID = 52, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/pokaradon.png", + Name = "UL Pokaradon", + BaseScore = 8, + IsUnlimited = true, + UnlimitedMonsterID = 115, + Type = FrontierMonsterType.Leviathan, + }, } }, { - Difficulty.Medium, new List + Difficulty.Medium, new List { - "Zenith★3 Espinas", - "Zenith★3 Hypnoc", - "Zenith★3 Khezu", - "Zenith★3 Daimyo Hermitaur", - "Zenith★3 Inagami", - "Zenith★3 Anorupatisu", - "Zenith★3 Toridcless", - "Zenith★3 Plesioth", - "Zenith★3 Rukodiora", - "Zenith★3 Rathalos", - "Zenith★3 Giaorugu", - "Zenith★3 Blangonga", - "Zenith★3 Akura Vashimu", - "Zenith★3 Tigrex", - "Zenith★3 Hyujikiki", - "Zenith★3 Midogaron", - "Zenith★3 Gasurabazura", - "Zenith★3 Taikun Zamuza", - "Zenith★3 Harudomerugu", - "Zenith★3 Baruragaru", - "Zenith★3 Gravios", - "Zenith★3 Doragyurosu", - "Zenith★3 Bogabadorumu", - "Starving Deviljho", - "Shifting Mi Ru", - "Twinhead Rajang", - "Lv9999 Disufiroa", - "Lv9999 Fatalis", - "Lv9999 Crimson Fatalis", - "Lv9999 Shantien", - "2nd District Duremudira", + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_akura_vashimu.gif", + Name = "Zenith★3 Akura Vashimu", + QuestIDs = new List { 23538, }, + BaseScore = 28, + Type = FrontierMonsterType.Carapaceon, + }, // z3 akura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_anorupatisu.gif", + Name = "Zenith★3 Anorupatisu", + QuestIDs = new List { 23720, }, + BaseScore = 36, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 anoru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_inagami.gif", + Name = "Zenith★3 Inagami", + QuestIDs = new List { 23646, }, + BaseScore = 40, + Type = FrontierMonsterType.ElderDragon, + }, // z3 inagami + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_espinas.gif", + Name = "Zenith★3 Espinas", + QuestIDs = new List { 23482, }, + BaseScore = 24, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 espi + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gasurabazura.gif", + Name = "Zenith★3 Gasurabazura", + QuestIDs = new List { 23670, }, + BaseScore = 36, + Type = FrontierMonsterType.BruteWyvern, + }, // z3 gasura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_plesioth.gif", + Name = "Zenith★3 Plesioth", + QuestIDs = new List { 23624, }, + BaseScore = 32, + Type = FrontierMonsterType.PiscineWyvern, + }, // z3 plesioth + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_giaorugu.gif", + Name = "Zenith★3 Giaorugu", + QuestIDs = new List { 23612, }, + BaseScore = 28, + Type = FrontierMonsterType.BruteWyvern, + }, // z3 giao + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gravios.gif", + Name = "Zenith★3 Gravios", + QuestIDs = new List { 23711, }, + BaseScore = 36, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 gravios + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_daimyo_hermitaur.gif", + Name = "Zenith★3 Daimyo Hermitaur", + QuestIDs = new List { 23478, }, + BaseScore = 24, + Type = FrontierMonsterType.Carapaceon, + }, // z3 daimyo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_tigrex.gif", + Name = "Zenith★3 Tigrex", + QuestIDs = new List { 23542, }, + BaseScore = 20, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 tigrex + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_blangonga.gif", + Name = "Zenith★3 Blangonga", + QuestIDs = new List { 23518, }, + BaseScore = 24, + Type = FrontierMonsterType.FangedBeast, + }, // z3 blango + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_doragyurosu.gif", + Name = "Zenith★3 Doragyurosu", + QuestIDs = new List { 23661, }, + BaseScore = 24, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 dora + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_toridcless.gif", + Name = "Zenith★3 Toridcless", + QuestIDs = new List { 23657, }, + BaseScore = 24, + Type = FrontierMonsterType.BirdWyvern, + }, // z3 torid + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_baruragaru.gif", + Name = "Zenith★3 Baruragaru", + QuestIDs = new List { 23715, }, + BaseScore = 36, + Type = FrontierMonsterType.Leviathan, + }, // z3 baru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hypnoc.gif", + Name = "Zenith★3 Hypnocatrice", + QuestIDs = new List { 23470, }, + BaseScore = 28, + Type = FrontierMonsterType.BirdWyvern, + }, // z3 hypnoc + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hyujikiki.gif", + Name = "Zenith★3 Hyujikiki", + QuestIDs = new List { 23608, }, + BaseScore = 32, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 hyuji + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_khezu.gif", + Name = "Zenith★3 Khezu", + QuestIDs = new List { 23474, }, + BaseScore = 24, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 khezu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_bogabadorumu.gif", + Name = "Zenith★3 Bogabadorumu", + QuestIDs = new List { 23707, }, + BaseScore = 24, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 boggy + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_midogaron.gif", + Name = "Zenith★3 Midogaron", + QuestIDs = new List { 23616, }, + BaseScore = 28, + Type = FrontierMonsterType.FangedBeast, + }, // z3 mido + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rathalos.gif", + Name = "Zenith★3 Rathalos", + QuestIDs = new List { 23522, }, + BaseScore = 24, + Type = FrontierMonsterType.FlyingWyvern, + }, // z3 rath + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rukodiora.gif", + Name = "Zenith★3 Rukodiora", + QuestIDs = new List { 23620, }, + BaseScore = 36, + Type = FrontierMonsterType.ElderDragon, + }, // z3 ruko + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_taikun_zamuza.gif", + Name = "Zenith★3 Taikun Zamuza", + QuestIDs = new List { 55925, }, + BaseScore = 36, + Type = FrontierMonsterType.Carapaceon, + }, // z3 taikun + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_harudomerugu.gif", + Name = "Zenith★3 Harudomerugu", + QuestIDs = new List { 55931, }, + BaseScore = 24, + Type = FrontierMonsterType.ElderDragon, + }, // z3 harudo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/starving_deviljho.png", + Name = "Starving Deviljho", + QuestIDs = new List { 55916, }, + BaseScore = 30, + Type = FrontierMonsterType.BruteWyvern, + }, // starving jho + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shifting_mi_ru.png", + Name = "Shifting Mi Ru", + QuestIDs = new List { Numbers.QuestIDShiftingMiRu, }, + BaseScore = 30, + Type = FrontierMonsterType.Other, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/twinhead_rajang.png", + Name = "Twinhead Rajangs", + QuestIDs = new List { Numbers.QuestIDTwinheadRajangsHistoric, }, + BaseScore = 20, + Type = FrontierMonsterType.FangedBeast, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_fatalis.png", + Name = "Lv9999 Fatalis", + QuestIDs = new List { Numbers.QuestIDLV9999Fatalis, }, + BaseScore = 35, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_crimson_fatalis.png", + Name = "Lv9999 Crimson Fatalis", + QuestIDs = new List { Numbers.QuestIDLV9999CrimsonFatalis, }, + BaseScore = 40, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/disufiroa.png", + Name = "Lv9999 Disufiroa", + QuestIDs = new List { Numbers.QuestIDLV9999Disufiroa, }, + BaseScore = 40, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/conquest_shantien.png", + Name = "Lv9999 Shantien", + QuestIDs = new List { Numbers.QuestIDLV9999Shantien, }, + BaseScore = 40, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/duremudira.png", + Name = "2nd Distric Duremudira", + QuestIDs = new List { Numbers.QuestIDSecondDistrictDuremudira, }, + BaseScore = 40, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/azure_rathalos.png", + Name = "UL Azure Rathalos", + BaseScore = 15, + IsUnlimited = true, + UnlimitedMonsterID = 49, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/pink_rathian.png", + Name = "UL Pink Rathian", + BaseScore = 14, + IsUnlimited = true, + UnlimitedMonsterID = 37, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/bright_hypnoc.png", + Name = "UL Bright Hypnoc", + BaseScore = 13, + IsUnlimited = true, + UnlimitedMonsterID = 78, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/purple_gypceros.png", + Name = "UL Purple Gypceros", + BaseScore = 13, + IsUnlimited = true, + UnlimitedMonsterID = 39, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/green_plesioth.png", + Name = "UL Green Plesioth", + BaseScore = 15, + IsUnlimited = true, + UnlimitedMonsterID = 46, + Type = FrontierMonsterType.PiscineWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/blue_yian_kut-ku.png", + Name = "UL Blue Yian Kut-Ku", + BaseScore = 12, + IsUnlimited = true, + UnlimitedMonsterID = 38, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/akura_jebia.png", + Name = "UL Akura Jebia", + BaseScore = 15, + IsUnlimited = true, + UnlimitedMonsterID = 84, + Type = FrontierMonsterType.Carapaceon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/orange_espinas.png", + Name = "UL Orange Espinas", + BaseScore = 20, + IsUnlimited = true, + UnlimitedMonsterID = 81, + Type = FrontierMonsterType.FlyingWyvern, + }, } }, { - Difficulty.Hard, new List + Difficulty.Hard, new List { - "Zenith★4 Espinas", - "Zenith★4 Hypnoc", - "Zenith★4 Khezu", - "Zenith★4 Daimyo Hermitaur", - "Zenith★4 Inagami", - "Zenith★4 Anorupatisu", - "Zenith★4 Toridcless", - "Zenith★4 Plesioth", - "Zenith★4 Rukodiora", - "Zenith★4 Rathalos", - "Zenith★4 Giaorugu", - "Zenith★4 Blangonga", - "Zenith★4 Akura Vashimu", - "Zenith★4 Tigrex", - "Zenith★4 Hyujikiki", - "Zenith★4 Midogaron", - "Zenith★4 Gasurabazura", - "Zenith★4 Taikun Zamuza", - "Zenith★4 Harudomerugu", - "Zenith★4 Baruragaru", - "Zenith★4 Gravios", - "Zenith★4 Doragyurosu", - "Zenith★4 Bogabadorumu", - "Howling Zinogre", - "Blinking Nargacuga", - "Arrogant Duremudira", - "Sparkling Zerureusu", - "Burning Freezing Elzelion", - "Blitzkrieg Bogabadorumu", - "Golden Deviljho", - "Upper Shiten Unknown", - "Upper Shiten Disufiroa", + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_akura_vashimu.gif", + Name = "Zenith★4 Akura Vashimu", + QuestIDs = new List { Numbers.QuestIDZ4AkuraVashimu, }, + BaseScore = 56, + Type = FrontierMonsterType.Carapaceon, + }, // z4 akura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_anorupatisu.gif", + Name = "Zenith★4 Anorupatisu", + QuestIDs = new List { Numbers.QuestIDZ4Anorupatisu, }, + BaseScore = 72, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 anoru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_inagami.gif", + Name = "Zenith★4 Inagami", + QuestIDs = new List { Numbers.QuestIDZ4Inagami, }, + BaseScore = 80, + Type = FrontierMonsterType.ElderDragon, + }, // z4 inagami + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_espinas.gif", + Name = "Zenith★4 Espinas", + QuestIDs = new List { Numbers.QuestIDZ4Espinas, }, + BaseScore = 48, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 espi + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gasurabazura.gif", + Name = "Zenith★4 Gasurabazura", + QuestIDs = new List { Numbers.QuestIDZ4Gasurabazura, }, + BaseScore = 80, + Type = FrontierMonsterType.BruteWyvern, + }, // z4 gasura + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_plesioth.gif", + Name = "Zenith★4 Plesioth", + QuestIDs = new List { Numbers.QuestIDZ4Plesioth, }, + BaseScore = 64, + Type = FrontierMonsterType.PiscineWyvern, + }, // z4 plesioth + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_giaorugu.gif", + Name = "Zenith★4 Giaorugu", + QuestIDs = new List { Numbers.QuestIDZ4Giaorugu, }, + BaseScore = 56, + Type = FrontierMonsterType.BruteWyvern, + }, // z4 giao + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_gravios.gif", + Name = "Zenith★4 Gravios", + QuestIDs = new List { Numbers.QuestIDZ4Gravios, }, + BaseScore = 72, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 gravios + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_daimyo_hermitaur.gif", + Name = "Zenith★4 Daimyo Hermitaur", + QuestIDs = new List { Numbers.QuestIDZ4DaimyoHermitaur, }, + BaseScore = 48, + Type = FrontierMonsterType.Carapaceon, + }, // z4 daimyo + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_tigrex.gif", + Name = "Zenith★4 Tigrex", + QuestIDs = new List { Numbers.QuestIDZ4Tigrex, }, + BaseScore = 40, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 tigrex + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_blangonga.gif", + Name = "Zenith★4 Blangonga", + QuestIDs = new List { Numbers.QuestIDZ4Blangonga, }, + BaseScore = 48, + Type = FrontierMonsterType.FangedBeast, + }, // z4 blango + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_doragyurosu.gif", + Name = "Zenith★4 Doragyurosu", + QuestIDs = new List { Numbers.QuestIDZ4Doragyurosu, }, + BaseScore = 48, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 dora + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_toridcless.gif", + Name = "Zenith★4 Toridcless", + QuestIDs = new List { Numbers.QuestIDZ4Toridcless, }, + BaseScore = 48, + Type = FrontierMonsterType.BirdWyvern, + }, // z4 torid + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_baruragaru.gif", + Name = "Zenith★4 Baruragaru", + QuestIDs = new List { Numbers.QuestIDZ4Baruragaru, }, + BaseScore = 72, + Type = FrontierMonsterType.Leviathan, + }, // z4 baru + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hypnoc.gif", + Name = "Zenith★4 Hypnocatrice", + QuestIDs = new List { Numbers.QuestIDZ4Hypnocatrice, }, + BaseScore = 56, + Type = FrontierMonsterType.BirdWyvern, + }, // z4 hypnoc + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_hyujikiki.gif", + Name = "Zenith★4 Hyujikiki", + QuestIDs = new List { Numbers.QuestIDZ4Hyujikiki, }, + BaseScore = 64, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 hyuji + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_khezu.gif", + Name = "Zenith★4 Khezu", + QuestIDs = new List { Numbers.QuestIDZ4Khezu, }, + BaseScore = 48, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 khezu + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_bogabadorumu.gif", + Name = "Zenith★4 Bogabadorumu", + QuestIDs = new List { Numbers.QuestIDZ4Bogabadorumu, }, + BaseScore = 48, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 boggy + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_midogaron.gif", + Name = "Zenith★4 Midogaron", + QuestIDs = new List { Numbers.QuestIDZ4Midogaron, }, + BaseScore = 56, + Type = FrontierMonsterType.FangedBeast, + }, // z4 mido + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rathalos.gif", + Name = "Zenith★4 Rathalos", + QuestIDs = new List { Numbers.QuestIDZ4Rathalos, }, + BaseScore = 48, + Type = FrontierMonsterType.FlyingWyvern, + }, // z4 rath + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_rukodiora.gif", + Name = "Zenith★4 Rukodiora", + QuestIDs = new List { Numbers.QuestIDZ4Rukodiora, }, + BaseScore = 72, + Type = FrontierMonsterType.ElderDragon, + }, // z4 ruko + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_taikun_zamuza.gif", + Name = "Zenith★4 Taikun Zamuza", + QuestIDs = new List { Numbers.QuestIDZ4TaikunZamuza, }, + BaseScore = 72, + Type = FrontierMonsterType.Carapaceon, + }, // z4 taikun + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenith_harudomerugu.gif", + Name = "Zenith★4 Harudomerugu", + QuestIDs = new List { Numbers.QuestIDZ4Harudomerugu, }, + BaseScore = 48, + Type = FrontierMonsterType.ElderDragon, + }, // z4 harudo + new BingoMonster + { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/howling_zinogre.png", + Name = "Howling Zinogre", + QuestIDs = new List { Numbers.QuestIDHowlingZinogreForest, Numbers.QuestIDHowlingZinogreHistoric, }, + BaseScore = 90, + Type = FrontierMonsterType.FangedWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/blinking_nargacuga.png", + Name = "Blinking Nargacuga", + QuestIDs = new List { Numbers.QuestIDBlinkingNargacugaForest, Numbers.QuestIDBlinkingNargacugaHistoric, }, + BaseScore = 90, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/arrogant_duremudira.png", + Name = "Arrogant Duremudira", + QuestIDs = new List { Numbers.QuestIDArrogantDuremudira, }, + BaseScore = 120, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/sparkling_zerureusu.png", + Name = "Sparkling Zerureusu", + QuestIDs = new List { Numbers.QuestIDSparklingZerureusu, }, + BaseScore = 110, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/burning_freezing_elzelion.png", + Name = "Burning Freezing Elzelion", + QuestIDs = new List { Numbers.QuestIDBurningFreezingElzelionTower, Numbers.QuestIDBurningFreezingElzelionHistoric, }, + BaseScore = 150, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/blitzkrieg_bogabadorumu.png", + Name = "Blitzkrieg Bogabadorumu", + QuestIDs = new List { Numbers.QuestIDBlitzkriegBogabadorumu, }, + BaseScore = 110, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/golden_deviljho.png", + Name = "Golden Deviljho", + QuestIDs = new List { Numbers.QuestIDStarvingDeviljhoArena, Numbers.QuestIDStarvingDeviljhoHistoric, }, + BaseScore = 100, + Type = FrontierMonsterType.BruteWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shiten_disufiroa.png", + Name = "Upper Shiten Disufiroa", + QuestIDs = new List { Numbers.QuestIDUpperShitenDisufiroa, }, + BaseScore = 120, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/shiten_unknown.png", + Name = "Upper Shiten Unknown", + QuestIDs = new List { Numbers.QuestIDUpperShitenUnknown, }, + BaseScore = 90, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/rebidiora.png", + Name = "UL Rebidiora", + BaseScore = 30, + IsUnlimited = true, + UnlimitedMonsterID = 108, + Type = FrontierMonsterType.ElderDragon, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/akantor.png", + Name = "UL Akantor", + BaseScore = 40, + IsUnlimited = true, + UnlimitedMonsterID = 77, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/silver_rathalos.png", + Name = "UL Silver Rathalos", + BaseScore = 35, + IsUnlimited = true, + UnlimitedMonsterID = 41, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gold_rathian.png", + Name = "UL Gold Rathian", + BaseScore = 34, + IsUnlimited = true, + UnlimitedMonsterID = 42, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/poborubarumu.png", + Name = "UL Poborubarumu", + BaseScore = 40, + IsUnlimited = true, + UnlimitedMonsterID = 131, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/berukyurosu.png", + Name = "UL Berukyurosu", + BaseScore = 35, + IsUnlimited = true, + UnlimitedMonsterID = 85, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/red_khezu.png", + Name = "UL Red Khezu", + BaseScore = 30, + IsUnlimited = true, + UnlimitedMonsterID = 45, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/forokururu.png", + Name = "UL Forokururu", + BaseScore = 40, + IsUnlimited = true, + UnlimitedMonsterID = 125, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/farunokku.png", + Name = "UL Farunokku", + BaseScore = 35, + IsUnlimited = true, + UnlimitedMonsterID = 114, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/silver_hypnoc.png", + Name = "UL Silver Hypnoc", + BaseScore = 40, + IsUnlimited = true, + UnlimitedMonsterID = 82, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/black_diablos.png", + Name = "UL Black Diablos", + BaseScore = 45, + IsUnlimited = true, + UnlimitedMonsterID = 43, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/zenaserisu.png", + Name = "UL Zenaserisu", + BaseScore = 50, + IsUnlimited = true, + UnlimitedMonsterID = 161, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/gurenzeburu.png", + Name = "UL Gurenzeburu", + BaseScore = 45, + IsUnlimited = true, + UnlimitedMonsterID = 96, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/black_gravios.png", + Name = "UL Black Gravios", + BaseScore = 45, + IsUnlimited = true, + UnlimitedMonsterID = 47, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/kuarusepusu.png", + Name = "UL Kuarusepusu", + BaseScore = 50, + IsUnlimited = true, + UnlimitedMonsterID = 105, + Type = FrontierMonsterType.Leviathan, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/white_espinas.png", + Name = "UL White Espinas", + BaseScore = 50, + IsUnlimited = true, + UnlimitedMonsterID = 90, + Type = FrontierMonsterType.FlyingWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/red_lavasioth.png", + Name = "UL Red Lavasioth", + BaseScore = 50, + IsUnlimited = true, + UnlimitedMonsterID = 79, + Type = FrontierMonsterType.PiscineWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/yian_garuga.png", + Name = "UL Yian Garuga", + BaseScore = 50, + IsUnlimited = true, + UnlimitedMonsterID = 40, + Type = FrontierMonsterType.BirdWyvern, + }, + new BingoMonster { + Image = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/monster/abiorugu.png", + Name = "UL Abiorugu", + BaseScore = 45, + IsUnlimited = true, + UnlimitedMonsterID = 104, + Type = FrontierMonsterType.BruteWyvern, + }, } }, }); diff --git a/MHFZ_Overlay/Models/Collections/BingoStartCosts.cs b/MHFZ_Overlay/Models/Collections/BingoStartCosts.cs new file mode 100644 index 00000000..21f6cf80 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/BingoStartCosts.cs @@ -0,0 +1,51 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The bingo start base costs. +/// +public static class BingoStartCosts +{ + public static ReadOnlyDictionary DifficultyCosts { get; } = new(new Dictionary + { + { + Difficulty.Easy, 0 + }, + { + Difficulty.Medium, 50 + }, + { + Difficulty.Hard, 250 + }, + { + Difficulty.Extreme, 1000 + }, + }); + + public static ReadOnlyDictionary GauntletBoostCosts { get; } = new (new Dictionary + { + { + GauntletBoost.None, 0 + }, + { + GauntletBoost.Zenith, 10 + }, + { + GauntletBoost.Solstice, 20 + }, + { + GauntletBoost.Musou, 30 + }, + }); + + public static int MusouElzelionBoostCost = 100; +} diff --git a/MHFZ_Overlay/Models/Collections/BingoUpgradeCostProgressions.cs b/MHFZ_Overlay/Models/Collections/BingoUpgradeCostProgressions.cs new file mode 100644 index 00000000..ba528519 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/BingoUpgradeCostProgressions.cs @@ -0,0 +1,76 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The bingo upgrade cost progressions. +/// +public static class BingoUpgradeCostProgressions +{ + public static ReadOnlyDictionary LinearCostProgressions { get; } = new(new Dictionary + { + { + BingoUpgradeType.MiddleSquareRerollChance, + new LevelProgressionLinear { InitialValue = 1000, ValueIncreasePerLevel = 990 } + }, + }); + + public static ReadOnlyDictionary ExponentialCostProgressions { get; } = new (new Dictionary + { + { + BingoUpgradeType.BaseScoreFlatIncrease, + new LevelProgressionExponential { InitialValue = 10, ValueIncreaseFactor = 2 } + }, + { + BingoUpgradeType.BaseScoreMultiplier, + new LevelProgressionExponential { InitialValue = 50, ValueIncreaseFactor = 1.8M } + }, + { + BingoUpgradeType.WeaponMultiplier, + new LevelProgressionExponential { InitialValue = 500, ValueIncreaseFactor = 1.4M } + }, + { + BingoUpgradeType.CartsScore, + new LevelProgressionExponential { InitialValue = 250, ValueIncreaseFactor = 1.6M } + }, + { + BingoUpgradeType.BonusScore, + new LevelProgressionExponential { InitialValue = 1200, ValueIncreaseFactor = 1.2M } + }, + { + BingoUpgradeType.MiddleSquareMultiplier, + new LevelProgressionExponential { InitialValue = 2500, ValueIncreaseFactor = 1.1M } + }, + { + BingoUpgradeType.ExtraCarts, + new LevelProgressionExponential { InitialValue = 1000, ValueIncreaseFactor = 2 } + }, + { + BingoUpgradeType.StartingCostReduction, + new LevelProgressionExponential { InitialValue = 800, ValueIncreaseFactor = 1.5M } + }, + { + BingoUpgradeType.BurningFreezingElzelionRerolls, + new LevelProgressionExponential { InitialValue = 500, ValueIncreaseFactor = 1.4M } + }, + { + BingoUpgradeType.BurningFreezingElzelionRerollChance, + new LevelProgressionExponential { InitialValue = 500, ValueIncreaseFactor = 1.4M } + }, + { + BingoUpgradeType.AchievementMultiplier, + new LevelProgressionExponential { InitialValue = 500, ValueIncreaseFactor = 1.4M } + }, + { + BingoUpgradeType.SecretAchievementMultiplier, + new LevelProgressionExponential { InitialValue = 500, ValueIncreaseFactor = 1.4M } + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/BingoUpgradeValueProgressions.cs b/MHFZ_Overlay/Models/Collections/BingoUpgradeValueProgressions.cs new file mode 100644 index 00000000..d23d9f11 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/BingoUpgradeValueProgressions.cs @@ -0,0 +1,76 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The bingo upgrade value progressions. +/// +public static class BingoUpgradeValueProgressions +{ + public static ReadOnlyDictionary ValueProgressions { get; } = new (new Dictionary + { + { + BingoUpgradeType.BaseScoreMultiplier, + new LevelProgressionLinear { InitialValue = 1.1M, ValueIncreasePerLevel = 0.1M } + }, + { + BingoUpgradeType.BaseScoreFlatIncrease, + new LevelProgressionLinear { InitialValue = 2, ValueIncreasePerLevel = 2 } + }, + { + BingoUpgradeType.CartsScore, + new LevelProgressionLinear { InitialValue = 3, ValueIncreasePerLevel = 3 } + }, + { + BingoUpgradeType.BonusScore, + new LevelProgressionLinear { InitialValue = 10, ValueIncreasePerLevel = 10 } + }, + { + BingoUpgradeType.MiddleSquareMultiplier, + new LevelProgressionLinear { InitialValue = 1.05M, ValueIncreasePerLevel = 0.05M } + }, + { + BingoUpgradeType.WeaponMultiplier, + new LevelProgressionLinear { InitialValue = 1.02M, ValueIncreasePerLevel = 0.02M } + }, + { + BingoUpgradeType.ExtraCarts, + new LevelProgressionLinear { InitialValue = 0, ValueIncreasePerLevel = 1 } + }, + { + BingoUpgradeType.StartingCostReduction, + new LevelProgressionLinear { InitialValue = 0.05M, ValueIncreasePerLevel = 0.05M } + }, + { + BingoUpgradeType.MiddleSquareRerollChance, + new LevelProgressionLinear { InitialValue = 0.0001M, ValueIncreasePerLevel = 0.0001M } + }, + { + BingoUpgradeType.BurningFreezingElzelionRerolls, + new LevelProgressionLinear { InitialValue = 1, ValueIncreasePerLevel = 1 } + }, + { + BingoUpgradeType.BurningFreezingElzelionRerollChance, + new LevelProgressionLinear { InitialValue = 0.01M, ValueIncreasePerLevel = 0.01M } + }, + { + BingoUpgradeType.AchievementMultiplier, + new LevelProgressionLinear { InitialValue = 0.001M, ValueIncreasePerLevel = 0.001M } + }, + { + BingoUpgradeType.SecretAchievementMultiplier, + new LevelProgressionLinear { InitialValue = 1, ValueIncreasePerLevel = 1 } + }, + { + BingoUpgradeType.BingoCompletionsMultiplier, + new LevelProgressionLinear { InitialValue = 1.1M, ValueIncreasePerLevel = 0.1M } + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/BingoUpgrades.cs b/MHFZ_Overlay/Models/Collections/BingoUpgrades.cs new file mode 100644 index 00000000..7dbc9f5f --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/BingoUpgrades.cs @@ -0,0 +1,319 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MHFZ_Overlay.Models.Structures; + +public static class BingoUpgrades +{ + public static ReadOnlyDictionary IDBingoUpgrade { get; } = new(new Dictionary + { + { + 0, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Gives a flat increase to the monster base score. Modifies bingo square score.", + MaxLevel = 10, + Name = "Base Score Increase", + Type = BingoUpgradeType.BaseScoreFlatIncrease, + } + }, + { + 1, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Multiplies the monster base score by a set multiplier. Modifies bingo square score.", + MaxLevel = 10, + Name = "Base Score Multiplier", + Type = BingoUpgradeType.BaseScoreMultiplier, + } + }, + { + 2, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier if the weapon type used in the quest corresponds to the weapon shown on the grid. Modifies bingo square score.", + MaxLevel = 10, + Name = "Weapon Type Multiplier", + Type = BingoUpgradeType.WeaponMultiplier, + } + }, + { + 3, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set amount depending on the amount of times you carted on the quest (e.g. if carts score is 30, then no carts means 30, 1 cart means 20, and 2 carts or more means 10 more points respectively). Modifies bingo square score.", + MaxLevel = 10, + Name = "Carts Score", + Type = BingoUpgradeType.CartsScore, + } + }, + { + 4, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Gives a flat final bonus score, unaffected by multipliers and is calculated last. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Bonus Score", + Type = BingoUpgradeType.BonusScore, + } + }, + { + 5, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier if the quest completed corresponds to the middle square of the bingo board. Modifies bingo square score.", + MaxLevel = 10, + Name = "Middle Square Multiplier", + Type = BingoUpgradeType.MiddleSquareMultiplier, + } + }, + { + 6, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the amount of carts allowed at the start of a bingo run. Faints from quest cancels are not counted, only completed quests are counted.", + MaxLevel = 4, + Name = "Extra Carts", + Type = BingoUpgradeType.ExtraCarts, + } + }, + { + 7, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Reduces the starting cost of a bingo run by a set percentage.", + MaxLevel = 10, + Name = "Starting Cost Reduction", + Type = BingoUpgradeType.StartingCostReduction, + } + }, + { + 8, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the chance of an extreme difficulty bingo board to have its middle square be rerolled at the start of a bingo run.", + MaxLevel = 100, + Name = "Middle Square Reroll Chance (Extreme Difficulty)", + Type = BingoUpgradeType.MiddleSquareRerollChance, + } + }, + { + 9, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the amount of rerolls for Burning Freezing Elzelion appearing in a random cell at the start of a bingo run. Can only be activated if the bingo board difficulty is set to Extreme.", + MaxLevel = 100, + Name = "Burning Freezing Elzelion Rerolls (Extreme Difficulty)", + Type = BingoUpgradeType.BurningFreezingElzelionRerolls, + } + }, + { + 10, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the chance for Burning Freezing Elzelion rerolls to succeed at the start of a bingo run. Can only be activated if the bingo board difficulty is set to Extreme.", + MaxLevel = 10, + Name = "Burning Freezing Elzelion Reroll Chance (Extreme Difficulty)", + Type = BingoUpgradeType.BurningFreezingElzelionRerollChance, + } + }, + { + 11, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier according to the amount of obtained achievements. Achievements that are secret do not count. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Achievements Multiplier", + Type = BingoUpgradeType.AchievementMultiplier, + } + }, + { + 12, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending on the amount of secret achievements obtained. Modifies bingo run final score", + MaxLevel = 5, + Name = "Secret Achievements Multiplier", + Type = BingoUpgradeType.SecretAchievementMultiplier, + } + }, + { + 13, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending on the amount of bingo runs completed. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Bingo Completions Multiplier", + Type = BingoUpgradeType.BingoCompletionsMultiplier, + } + }, + { + 14, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the square completed contains a zenith monster. Requires Gauntlet Boost to activate (1 Zenith Gauntlet item to use). Modifies bingo square score.", + MaxLevel = 10, + Name = "Zenith Square Multiplier", + Type = BingoUpgradeType.ZenithMultiplier, + } + }, + { + 15, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the square completed contains a conquest or shiten monster. Requires Gauntlet Boost to activate (1 Solstice Gauntlet item to use). Modifies bingo square score.", + MaxLevel = 10, + Name = "Conquest/Shiten Square Multiplier", + Type = BingoUpgradeType.SolsticeMultiplier, + } + }, + { + 16, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the square completed contains a musou monster. Requires Gauntlet Boost to activate (1 Musou Gauntlet item to use). Modifies bingo square score.", + MaxLevel = 10, + Name = "Musou Square Multiplier", + Type = BingoUpgradeType.MusouMultiplier, + } + }, + { + 17, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the completed bingo run was done with a horizontal line. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Horizontal Line Bingo Completion Multiplier", + Type = BingoUpgradeType.HorizontalLineCompletionMultiplier, + } + }, + { + 18, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the completed bingo run was done with a vertical line. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Vertical Line Bingo Completion Multiplier", + Type = BingoUpgradeType.VerticalLineCompletionMultiplier, + } + }, + { + 19, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending if the completed bingo run was done with a diagonal line. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Diagonal Line Bingo Completion Multiplier", + Type = BingoUpgradeType.DiagonalLineCompletionMultiplier, + } + }, + { + 20, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the score by a set multiplier depending on the time it took to comple the bingo run, as shown in the timer close to the bingo board. Modifies bingo run final score.", + MaxLevel = 10, + Name = "Bingo Run Time Completion Multiplier", + Type = BingoUpgradeType.RealTimeMultiplier, + } + }, + { + 21, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the chance of finding a page in a cell at board generation.", + MaxLevel = 10, + Name = "Page Finder Chance", + Type = BingoUpgradeType.PageFinderChance, + } + }, + { + 22, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the chance of finding an ancient dragon part's scraps in a cell at board generation.", + MaxLevel = 10, + Name = "Scrap Finder Chance", + Type = BingoUpgradeType.AncientDragonPartScrapChance, + } + }, + { + 23, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the rate for the compound interest of the currently stored bingo points. It compounds x times where x is the amount of bingo cells completed in a run. The compound interest is calculated at the end of a run, taking into account the points obtained in the run plus the currently stored points.", + MaxLevel = 10, + Name = "Bingo Points Compound Interest Rate", + Type = BingoUpgradeType.BingoPointsCompoundInterestRate, + } + }, + { + 24, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Reduces the cost of upgrades.", + MaxLevel = 10, + Name = "Upgrade Cost Reduction", + Type = BingoUpgradeType.BingoShopUpgradeReduction, + } + }, + { + 25, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the rate or speed at which the transcend meter fills.", + MaxLevel = 10, + Name = "Transcend Meter Fill Rate", + Type = BingoUpgradeType.TranscendMeterFillRate, + } + }, + { + 26, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Reduces the cost of a true transcend. Lowers the transcend meter capacity, which might be a drawback when using normal transcend.", + MaxLevel = 10, + Name = "True Transcend Cost Reduction", + Type = BingoUpgradeType.TrueTranscendCostReduction, + } + }, + { + 27, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Decreases the rate at which the transcend meter drains.", + MaxLevel = 10, + Name = "Transcend Meter Drain Reduction", + Type = BingoUpgradeType.TranscendMeterDrainReduction, + } + }, + { + 28, new BingoUpgrade + { + CurrentLevel = 1, + Description = "Increases the grace period for obtaining the maximum time score.", + MaxLevel = 10, + Name = "Maximum Time Score Grace Period Increase", + Type = BingoUpgradeType.MaxTimeScoreGracePeriodIncrease, + } + }, + { + 29, new BingoUpgrade + { + CurrentLevel = 1, + Description = "You found a very old book, the shop owner says you can take it if you complete a bingo.", + MaxLevel = 2, + Name = "Book of Secrets Tome I", + Type = BingoUpgradeType.BookOfSecretsTomeOne, + IsUnlocked = true, + } + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/ChallengeAncientDragonParts.cs b/MHFZ_Overlay/Models/Collections/ChallengeAncientDragonParts.cs new file mode 100644 index 00000000..bd265804 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/ChallengeAncientDragonParts.cs @@ -0,0 +1,324 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; +using Wpf.Ui.Hardware; + +/// +/// The challenge ancient dragon parts list. +/// +public static class ChallengeAncientDragonParts +{ + public static ReadOnlyDictionary> TierParts { get; } = InitializeParts(); + + public static ReadOnlyDictionary> InitializeParts() + { + var tierParts = new Dictionary> + { + { + 0, new List + { + new ChallengeAncientDragonPart + { + InactivePartDescription = "The head of the dragon. Presents skull fractures.", + ActivePartDescription = "Imposing head, reminiscent of a true dragon's. With three distinct horns atop it, the central horn stands tall and vibrant in yellow, flanked by two shorter, white horns that curve downwards. A visor-like structure conceals any hint of visible eyes, but a fierce maw is fully exposed, exuding a sense of power.", + Name = "Cranium", + InactivePartImageLink = @"Assets/Icons/Head_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonCranium.xaml", + IsSource = true, + Effect = "Unlock the statistics tab.", + SynergyEffect = @"Unlock all features of the statistics tab. +Unlock an upgrade for increasing the chance of finding missing Book of Secrets pages. +Unlock an upgrade for increasing the chance of finding an ancient dragon part's scraps.", + SourceEffect = "None.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 100 }, + { FrontierMonsterType.Carapaceon, 1 }, + { FrontierMonsterType.FlyingWyvern, 1 }, + { FrontierMonsterType.BruteWyvern, 1 }, + { FrontierMonsterType.PiscineWyvern, 1 }, + { FrontierMonsterType.BirdWyvern, 1 }, + { FrontierMonsterType.FangedBeast, 1 }, + { FrontierMonsterType.Leviathan, 1 }, + { FrontierMonsterType.FangedWyvern, 1 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "The battle-worn armor of the dragon.", + ActivePartDescription = "Encased in grey, steel armor, it forms a protective shell around the core of its being. The armor is particularly concentrated on the chest, back, and limbs, providing a robust defense against adversaries. White marbling patterns adorn parts of the thorax, adding an intricate touch to the fierce creature's appearance.", + Name = "Thorax", + InactivePartImageLink = @"Assets/Icons/Sturdy_Shell_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonThorax.xaml", + Effect = "Unlock Extreme difficulty and related upgrades.", + SynergyEffect = "Gain an extra cart in Easy/Medium/Hard difficulty and two extra carts in Extreme difficulty.", + SourceEffect = "Carts from the first 3 squares are not penalized, both in points or in the carts counter going down.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 10 }, + { FrontierMonsterType.Carapaceon, 100 }, + { FrontierMonsterType.FlyingWyvern, 5 }, + { FrontierMonsterType.BruteWyvern, 10 }, + { FrontierMonsterType.PiscineWyvern, 5 }, + { FrontierMonsterType.BirdWyvern, 10 }, + { FrontierMonsterType.FangedBeast, 5 }, + { FrontierMonsterType.Leviathan, 10 }, + { FrontierMonsterType.FangedWyvern, 10 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "Wings with holes presenting all throughout.", + ActivePartDescription = "Massive, membranous wings sprout from the sides, granting it the gift of flight. These wings bear testament to the creature's draconic heritage, allowing it to soar through the skies with an awe-inspiring presence. Their size and structure mirror those of legendary dragons, giving it both a fearsome aspect and unmatched aerial capabilities.", + Name = "Wings", + InactivePartImageLink = @"Assets/Icons/Wing_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonWings.xaml", + Effect = "Unlock the ability to randomly rearrange cells at a cost.", + SynergyEffect = "Unlock the ability to rearrange the weapon bonuses up to 3 cells of your choice per run once, at a cost.", + SourceEffect = "Unlock the ability to rearrange up to 10 cells of your choice per run once, without any cost.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 25 }, + { FrontierMonsterType.Carapaceon, 1 }, + { FrontierMonsterType.FlyingWyvern, 100 }, + { FrontierMonsterType.BruteWyvern, 1 }, + { FrontierMonsterType.PiscineWyvern, 1 }, + { FrontierMonsterType.BirdWyvern, 10 }, + { FrontierMonsterType.FangedBeast, 1 }, + { FrontierMonsterType.Leviathan, 1 }, + { FrontierMonsterType.FangedWyvern, 1 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "Arms with many scars, look like they came from some black dragon.", + ActivePartDescription = "A testament to both strength and grace. Muscular and sinewy, these limbs are clad in the same formidable steel armor that encases its body. From shoulder to claw-tipped digits, the forelimbs exude power and purpose. Despite their bulk, they carry an undeniable agility, capable of precise movements and devastating strikes.", + Name = "Forelimbs", + InactivePartImageLink = @"Assets/Icons/Flipper_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonForelimbs.xaml", + Effect = "Unlock an upgrade for gaining more bingo points with compound interest.", + SynergyEffect = "Unlock an upgrade for reducing the cost of upgrades.", + SourceEffect = "Unlock the ability to purchase an upgrade for free once.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 25 }, + { FrontierMonsterType.Carapaceon, 10 }, + { FrontierMonsterType.FlyingWyvern, 1 }, + { FrontierMonsterType.BruteWyvern, 25 }, + { FrontierMonsterType.PiscineWyvern, 1 }, + { FrontierMonsterType.BirdWyvern, 10 }, + { FrontierMonsterType.FangedBeast, 25 }, + { FrontierMonsterType.Leviathan, 10 }, + { FrontierMonsterType.FangedWyvern, 25 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "Legs so badly damaged that you can see the bone of the dragon in some places.", + ActivePartDescription = "Providing a stable and mighty foundation, these limbs, clad in the same steel armor as the rest of its body, are capable of propelling the creature forward with incredible force. They allow for agile movement and powerful strikes, making them a crucial asset in combat.", + Name = "Hindlegs", + InactivePartImageLink = @"Assets/Icons/Leg_Carve_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonHindlegs.xaml", + Effect = "Unlock an upgrade for filling the transcend meter faster.", + SynergyEffect = "Unlock an upgrade for reducing the cost of a true transcend.", + SourceEffect = @"Unlock an upgrade for decreasing the rate at which the transcend meter drains. +Unlock an upgrade that increases the grace period for obtaining the maximum time score. +Unlock an upgrade for increasing the time score multiplier.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 1 }, + { FrontierMonsterType.Carapaceon, 1 }, + { FrontierMonsterType.FlyingWyvern, 1 }, + { FrontierMonsterType.BruteWyvern, 50 }, + { FrontierMonsterType.PiscineWyvern, 50 }, + { FrontierMonsterType.BirdWyvern, 1 }, + { FrontierMonsterType.FangedBeast, 10 }, + { FrontierMonsterType.Leviathan, 10 }, + { FrontierMonsterType.FangedWyvern, 10 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A tail that was cut in the middle.", + ActivePartDescription = "A sturdy and stout tail extends from its rear, completing its fearsome form. This tail, like the rest of its body, is encased in armor, reinforcing its defensive capabilities. The tail's design is not just for appearance; it contributes to the creature's balance and agility, ensuring that it maintains control even during its most dynamic maneuvers.", + Name = "Tail", + InactivePartImageLink = @"Assets/Icons/Tail_Icon_White.xaml", + ActivePartImageLink = @"Assets/Icons/AncientDragonTail.xaml", + Effect = "Unlock the ability to keep a certain amount of your current bingo points after doing a true transcend. A maximum of 20,000 Bingo Points can be transferred if you have 100,000 Bingo Points or more.", + SynergyEffect = "Unlock the ability to keep one upgrade in its first level after doing a true transcend, at a cost.", + SourceEffect = "Unlock the ability to exchange gauntlet gems for bingo points, and vice versa.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 1 }, + { FrontierMonsterType.ElderDragon, 10 }, + { FrontierMonsterType.Carapaceon, 1 }, + { FrontierMonsterType.FlyingWyvern, 20 }, + { FrontierMonsterType.BruteWyvern, 1 }, + { FrontierMonsterType.PiscineWyvern, 50 }, + { FrontierMonsterType.BirdWyvern, 10 }, + { FrontierMonsterType.FangedBeast, 1 }, + { FrontierMonsterType.Leviathan, 50 }, + { FrontierMonsterType.FangedWyvern, 1 }, + }, + }, + } + }, + { + 1, new List + { + new ChallengeAncientDragonPart + { + InactivePartDescription = "A black colored goo which seems to have been something else eons ago.", + ActivePartDescription = "A technological marvel installed by the Ancient Civilization. This advanced component enables the creature to process and manage its vast elemental powers seamlessly. The Cognisphere acts as a bridge between the creature's consciousness and its immense capabilities, allowing for swift transitions between different elements and tactics.", + Name = "Cognisphere", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + IsSource = true, + Effect = "Unlock the statistics tab.", + SynergyEffect = @"Unlock all features of the statistics tab. +Unlock an upgrade for increasing the chance of finding missing Book of Secrets pages. +Unlock an upgrade for increasing the chance of finding an ancient dragon part's scraps.", + SourceEffect = "None.", + GemsRequiredForScrap = new Dictionary + { + { FrontierMonsterType.Other, 0 }, + { FrontierMonsterType.ElderDragon, 0 }, + { FrontierMonsterType.Carapaceon, 0 }, + { FrontierMonsterType.FlyingWyvern, 0 }, + { FrontierMonsterType.BruteWyvern, 0 }, + { FrontierMonsterType.PiscineWyvern, 0 }, + { FrontierMonsterType.BirdWyvern, 0 }, + { FrontierMonsterType.FangedBeast, 0 }, + { FrontierMonsterType.Leviathan, 0 }, + { FrontierMonsterType.FangedWyvern, 0 }, + }, + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A heart that beats no more.", + ActivePartDescription = "The heartbeat of the creature, a pulsating core that sustains its existence. Crafted by the Ancient Civilization, this intricate component ensures that the amalgamation of Elder Dragon parts remains stable. It grants the creature the ability to harness its elemental powers without the risk of losing control, making it a linchpin of the creature's formidable abilities.", + Name = "Vital Nexus", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A visible component hidden in the depths of the being.", + ActivePartDescription = "Within the depths of the creature's being resides the Invisible Core, a hidden gem of technology that regulates its elemental energies. Imperceptible from the outside, this core works tirelessly to harmonize the conflicting powers within, preventing catastrophic imbalances and maintaining the creature's dominance in battle.", + Name = "Invisible Core", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "Claws that lack luster and sharpness.", + ActivePartDescription = "At the terminus of the creature's forelimbs are the fearsome True Talonclaws. These lethal appendages are a marvel of design, infused with the very essence of draconic power. Each claw is a masterpiece of organic and synthetic fusion, resembling long, wickedly sharp blades. Coated in a metallic sheen that glints in the light, they hint at their formidable potential. Upon closer inspection, intricate patterns trace along the surface, reflecting the elemental energies they can harness. When unleashed, the True Talonclaws become conduits of destruction. With a swing, they can conjure torrents of fire, surges of electricity, icy shards, or waves of water. Each strike is a symphony of destruction, a manifestation of the dragon's mastery over the elements.", + Name = "True Talonclaws", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "Rusted armor, to the point of decay.", + ActivePartDescription = "The Daora Plating adorns the creature's body, forming an unyielding shield against all manner of attacks. Named after the dragons that contributed to its creation, this steel armor offers unparalleled protection, allowing the creature to withstand assaults that would shatter lesser opponents.", + Name = "Daora Plating", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A component that seems to have lost its fuel.", + ActivePartDescription = "The embodiment of the creature's mastery over fire. It manifests as a blazing aura that envelops the dragon, scorching foes that dare to come close. This fierce display of pyrokinetic might strikes fear into the hearts of its adversaries, leaving them vulnerable to the creature's relentless onslaught.", + Name = "Hellfire Aegis", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + } + }, + { + 2, new List + { + new ChallengeAncientDragonPart + { + InactivePartDescription = "A component that looks like fragile glass.", + ActivePartDescription = "At the core of its being, the Ethereal Matrix exists as a nexus of power. It interconnects the creature's various elemental abilities, allowing for seamless transitions between different forms of devastation. This matrix symbolizes the culmination of the Ancient Civilization's technological prowess, enabling the creature to wield the might of dragons with unparalleled precision and potency.", + Name = "Ethereal Matrix", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + IsSource = true, + Effect = "Unlock the statistics tab.", + SynergyEffect = @"Unlock all features of the statistics tab. +Unlock an upgrade for increasing the chance of finding missing Book of Secrets pages. +Unlock an upgrade for increasing the chance of finding an ancient dragon part's scraps.", + SourceEffect = "None.", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "You sense that there should be something here, but don't know what.", + ActivePartDescription = "The Empyrean Aura radiates from afar, signifying its dominion over various elemental forces. This ethereal glow shifts and shimmers, reflecting the creature's adaptability as it seamlessly transitions between fire, water, thunder, ice, and draconic energy. The Empyrean Aura is both a symbol of its power and a harbinger of imminent destruction. Rumors circulate that this aura also commands the monsters of the Sky Corridor.", + Name = "Empyrean Aura", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A tattered cloak.", + ActivePartDescription = "Wrapped around the creature's form, the Conquerors' Cloak is a manifestation of its indomitable will. Woven from the threads of victory and conquest, this intangible shroud bolsters the dragon's resolve, empowering it to face any challenge with unwavering determination. It serves as a reminder that the creature is a force to be reckoned with. Should the creature be challenged, enemies will be struck down by black lightning. It is said that those who dare be near the cloak either faints or suffer a worse fate. It can also empower the Vigorous Armament.", + Name = "Conquerors' Cloak", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A component that seems to emit white smoke.", + ActivePartDescription = "A crucial aspect of the creature's design. This intricate network of jet-black steel and innovation envelops the creature's organic body, fortifying it against the challenges of battle. With a focus on protection, the armament ensures that the amalgamation of Elder Dragon parts remains intact, allowing the creature to endure and prevail against even the most relentless foes. Synergizes well with the Conquerors' Cloak.", + Name = "Vigorous Armament", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A component that when touched feels as if your cognition slows down.", + ActivePartDescription = "Enhances the dragon's agility, allowing it to move with unparalleled swiftness. This mystical garment bestows the gift of incredible speed upon the creature, enabling it to close distances swiftly or evade attacks with finesse. Its flowing presence is a testament to the creature's prowess in combat.", + Name = "Alacrity Mantle", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + new ChallengeAncientDragonPart + { + InactivePartDescription = "A component that smells pretty bad.", + ActivePartDescription = "A manifestation of the dragon's mastery over the elements. With each exhalation, it can unleash torrents of elemental energy, shaping them into devastating attacks. This breath is a deadly tool, enabling the creature to unleash destructive forces upon its adversaries, leaving chaos in its wake.", + Name = "Spirit Breath", + InactivePartImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }, + } + }, + }; + + // Set the NextSynergyPart property for each part to establish the circular synergy relationship + for (int i = 0; i < tierParts.Count; i++) + { + for (int j = 0; i < tierParts[i].Count; i++) + { + int nextIndex = (j + 1) % tierParts[i].Count; // Use modulus to loop back to 0 at the end + tierParts[i][j].NextSynergyPart = tierParts[i][nextIndex]; + } + } + + // Validate that there is exactly one source part per tier + foreach (var tier in tierParts.Values) + { + int sourcePartCount = tier.Count(part => part.IsSource); + if (sourcePartCount != 1) + { + throw new InvalidOperationException($"Tier contains {sourcePartCount} source parts. There should be exactly one source part per tier."); + } + } + + + return new ReadOnlyDictionary>(tierParts); + } +} diff --git a/MHFZ_Overlay/Models/Collections/ChallengeBookOfSecretsChapters.cs b/MHFZ_Overlay/Models/Collections/ChallengeBookOfSecretsChapters.cs new file mode 100644 index 00000000..64480ea7 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/ChallengeBookOfSecretsChapters.cs @@ -0,0 +1,416 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using MHFZ_Overlay.Models.Structures; + +public static class ChallengeBookOfSecretsChapters +{ + public static ReadOnlyDictionary IDChapters { get; } = InitializeChapters(); + + public static ReadOnlyDictionary InitializeChapters() + { + var idChapters = new Dictionary + { + { + 0, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo I", + PagesRequired = 0, + Description = "Shop: Unlocks Base Score Increase and Base Score Multiplier", + Details = "You unlocked the knowledge of bingo! Now you can purchase upgrades in the shop.", + } + }, + { + 1, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Felynes", + PagesRequired = 0, + Description = "Investigation: Research more about Felynes.", + Details = "A partnya was found who can help us obtain more book of secrets pages. With these pages, you can unlock more book of secrets chapters.", + } + }, + { + 2, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Weapon Mastery", + PagesRequired = 2, + Description = "Shop: Unlocks Weapon Type Multiplier.", + Details = "Equipping the weapon shown in the monster cells gives more points!", + } + }, + { + 3, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Veggie Elders", + PagesRequired = 3, + Description = "Investigation: Research more about Veggie Elders.", + Details = "Talking to the veggie elders now gives more pages in exchange for bingo points.", + } + }, + { + 4, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo II", + PagesRequired = 10, + Description = "Shop: Unlocks Carts Score.", + Details = "More bingo knowledge!", + } + }, + { + 5, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo III", + PagesRequired = 20, + Description = "Shop: Unlocks Bonus Score.", + Details = "This bingo upgrade may not seem like much, but it adds up!" + } + }, + { + 6, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo IV", + PagesRequired = 25, + Description = "Shop: Unlocks Middle Square Multiplier.", + Details = "A bingo's middle square is important.", + } + }, + { + 7, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo V", + PagesRequired = 30, + Description = "Shop: Unlocks Extra Carts.", + Details = "Now bingo runs won't be as risky!", + } + }, + { + 8, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo VI", + PagesRequired = 40, + Description = "Shop: Unlocks Starting Cost Reduction.", + Details = "You gained more negotiation powers.", + } + }, + { + 9, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Extreme Style I", + PagesRequired = 0, + Description = "Shop: Unlocks Middle Square Reroll Chance (Extreme Difficulty).", + Details = "You go back a few pages to see who the author of this chapter is. It reads: Patchouli.", + } + }, + { + 10, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Extreme Style II", + PagesRequired = 50, + Description = "Shop: Unlocks Burning Freezing Elzelion Rerolls (Extreme Difficulty).", + Details = "Musou Elzelion gives the most points out of any monster.", + } + }, + { + 11, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Extreme Style III", + PagesRequired = 100, + Description = "Shop: Unlocks Burning Freezing Elzelion Reroll Chance (Extreme Difficulty).", + Details = "Musou Elzelion will show more often!", + } + }, + { + 12, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Achievements I", + PagesRequired = 60, + Description = "Shop: Unlocks Achievements Multiplier.", + Details = "I love achievements.", + } + }, + { + 13, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Achievements II", + PagesRequired = 70, + Description = "Shop: Unlocks Secret Achievements Multiplier.", + Details = "I'll tell you a secret: one of the secret achievements are in the configuration menu." + } + }, + { + 14, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo VII", + PagesRequired = 80, + Description = "Shop: Unlocks Bingo Completions Multiplier.", + Details = "The more bingo runs you do, the more bingo points you get!" + } + }, + { + 15, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Monsters I", + PagesRequired = 90, + Description = "Shop: Unlocks Zenith Square Multiplier.", + Details = "You now have Bingo Up.", + } + }, + { + 16, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Monsters II", + PagesRequired = 100, + Description = "Shop: Unlocks Conquest/Shiten Square Multiplier.", + Details = "Bingo Lv9999 here we go!", + } + }, + { + 17, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Monsters III", + PagesRequired = 120, + Description = "Shop: Unlocks Musou Square Multiplier.", + Details = "These quest time limits sure are something.", + } + }, + { + 18, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Bingo VIII", + PagesRequired = 140, + Description = "Shop: Unlocks Horizontal Line Bingo Completion Multiplier, Vertical Line Bingo Completion Multiplier and Diagonal Line Bingo Completion Multiplier.", + Details = "B I N G O" + } + }, + { + 19, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Time I", + PagesRequired = 0, + Description = "Shop: Unlocks Bingo Run Time Completion Multiplier.", + Details = "We speedrunning now?", + } + }, + { + 20, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Scavengers I", + PagesRequired = 0, + Description = "Shop: Unlocks an upgrade for increasing the chance of finding missing Book of Secrets pages.", + Details = "Looks like these monsters were also hiding some pages.", + } + }, + { + 21, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Scavengers II", + PagesRequired = 100, + Description = "Shop: Unlocks an upgrade for increasing the chance of finding an ancient dragon part's scraps.", + Details = "Looks like some of these monsters were near an ancient dragon part, no wonder they are so strong.", + } + }, + { + 22, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Actuaries I", + PagesRequired = 0, + Description = "Shop: Unlocks an upgrade for gaining more bingo points with compound interest.", + Details = "I wonder what number do you get if you compound something countinously.", + } + }, + { + 23, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Actuaries II", + PagesRequired = 100, + Description = "Shop: Unlocks an upgrade for reducing the cost of upgrades.", + Details = "So this also reduces the cost of its own upgrade?", + } + }, + { + 24, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Transcendence I", + PagesRequired = 100, + Description = "Investigation: Reseach about an ancient technique.", + Details = "Transcendence is now unlocked!" + } + }, + { + 25, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Transcendence II", + PagesRequired = 120, + Description = "Shop: Unlocks an upgrade for filling the transcend meter faster.", + Details = "We can transcend faster now.", + } + }, + { + 26, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Transcendence III", + PagesRequired = 140, + Description = "Shop: Unlock an upgrade for reducing the cost of a true transcend.", + Details = "This might not be too beneficial for a normal transcend.", + } + }, + { + 27, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Transcendence IV", + PagesRequired = 160, + Description = "Shop: Unlocks an upgrade for decreasing the rate at which the transcend meter drains.", + Details = "This greatly helps in finishing a run before transcend runs out.", + } + }, + { + 28, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of Time II", + PagesRequired = 180, + Description = "Shop: Unlocks an upgrade that increases the grace period for obtaining the maximum time score.", + Details = "We don't have to be as fast as those speedrunners now.", + } + }, + { + 29, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of the Ancient Civilization I", + PagesRequired = 200, + Description = "Investigation: Research more about the Ancient Civilization.", // new tab + Details = "An investigation into the ancient civilization lead us to explore some ancient ruins near Schrade. During exploration you found some scattered ruins of an ancient dragon's parts and what appears to be a still working gauntlet." + } + }, + { + 30, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of the Ancient Civilization II", + PagesRequired = 250, + Description = "Investigation: Research more about ancient engineering.", // scraps + Details = "If we want to rebuild this ancient dragon, we should grab some scraps. Let's see if our Partnyaa and the Veggie Elder can help us." + } + }, + { + 31, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of the Ancient Civilization III", + PagesRequired = 300, + Description = "Investigation: Research more about an ancient technique.", // true transcend + Details = "This power... should not be possible. The problem is, if we use it then we will be drained for a very long time, or worse. The least risky way to use it is if we have our transcend meter at 100% first. Let's see if this can help us rebuild the ancient dragon!" + } + }, + { + 32, new ChallengeBookOfSecretsChapter + { + Name = "Knowledge of the Ancient Civilization IV", + PagesRequired = 500, + Description = "Investigation: Research more about ancient documents.", // blueprint at all 6 parts req. + Details = "We managed to find a way to recreate this ancient dragon and even improve its design, and wrote down the knowledge into a blueprint. The blueprint can be used to rebuild a better ancient dragon in another challenge." + } + }, + }; + + idChapters = AddBingoChapters(idChapters); + idChapters = AddBingoUpgrades(idChapters); + idChapters = AddBingoRequiredParts(idChapters); + + //chapters["chapter2"].Prerequisites.Add(chapters["chapter1"]); + + return new ReadOnlyDictionary(idChapters); + } + + private static Dictionary AddBingoRequiredParts(Dictionary idChapters) + { + idChapters[9].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][1], }); + idChapters[19].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][4], }); + idChapters[20].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][0], }); + idChapters[22].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][3], }); + idChapters[25].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][4], }); + idChapters[32].ChallengeAncientDragonPartsRequired.AddRange(new List { ChallengeAncientDragonParts.TierParts[0][0], ChallengeAncientDragonParts.TierParts[0][1], ChallengeAncientDragonParts.TierParts[0][2], ChallengeAncientDragonParts.TierParts[0][3], ChallengeAncientDragonParts.TierParts[0][4], ChallengeAncientDragonParts.TierParts[0][5], }); + + return idChapters; + } + + private static Dictionary AddBingoChapters(Dictionary idChapters) + { + idChapters[0].UnlockedChapters.AddRange(new List { idChapters[2], idChapters[4] }); + idChapters[1].UnlockedChapters.AddRange(new List { idChapters[3], }); + + idChapters[3].UnlockedChapters.AddRange(new List { idChapters[29], }); + + idChapters[4].UnlockedChapters.AddRange(new List { idChapters[5], }); + idChapters[5].UnlockedChapters.AddRange(new List { idChapters[6], }); + idChapters[6].UnlockedChapters.AddRange(new List { idChapters[7] }); + idChapters[7].UnlockedChapters.AddRange(new List { idChapters[8] }); + + idChapters[9].UnlockedChapters.AddRange(new List { idChapters[10] }); + idChapters[10].UnlockedChapters.AddRange(new List { idChapters[11] }); + + idChapters[7].UnlockedChapters.AddRange(new List { idChapters[12] }); + idChapters[12].UnlockedChapters.AddRange(new List { idChapters[13] }); + + idChapters[7].UnlockedChapters.AddRange(new List { idChapters[15] }); + idChapters[15].UnlockedChapters.AddRange(new List { idChapters[16] }); + idChapters[16].UnlockedChapters.AddRange(new List { idChapters[17] }); + + idChapters[8].UnlockedChapters.AddRange(new List { idChapters[14] }); + idChapters[14].UnlockedChapters.AddRange(new List { idChapters[18] }); + + idChapters[19].UnlockedChapters.AddRange(new List { idChapters[28] }); + + idChapters[20].UnlockedChapters.AddRange(new List { idChapters[21] }); + + idChapters[22].UnlockedChapters.AddRange(new List { idChapters[23] }); + + idChapters[29].UnlockedChapters.AddRange(new List { idChapters[24], idChapters[30], }); + + idChapters[24].UnlockedChapters.AddRange(new List { idChapters[25], }); + idChapters[25].UnlockedChapters.AddRange(new List { idChapters[26], }); + idChapters[26].UnlockedChapters.AddRange(new List { idChapters[27], }); + + idChapters[30].UnlockedChapters.AddRange(new List { idChapters[31], }); + idChapters[31].UnlockedChapters.AddRange(new List { idChapters[32], }); + + return idChapters; + } + + private static Dictionary AddBingoUpgrades(Dictionary idChapters) + { + idChapters[0].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[0], BingoUpgrades.IDBingoUpgrade[1] }); + idChapters[2].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[2], }); + idChapters[4].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[3], }); + idChapters[5].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[4], }); + idChapters[6].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[5], }); + idChapters[7].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[6], }); + idChapters[8].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[7], }); + idChapters[9].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[8], }); + idChapters[10].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[9], }); + idChapters[11].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[10], }); + idChapters[12].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[11], }); + idChapters[13].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[12], }); + idChapters[14].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[13], }); + idChapters[15].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[14], }); + idChapters[16].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[15], }); + idChapters[17].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[16], }); + idChapters[18].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[17], BingoUpgrades.IDBingoUpgrade[18], BingoUpgrades.IDBingoUpgrade[19], }); + idChapters[19].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[20], }); + idChapters[20].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[21], }); + idChapters[21].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[22], }); + idChapters[22].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[23], }); + idChapters[23].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[24], }); + + idChapters[25].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[25], }); + idChapters[26].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[26], }); + idChapters[27].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[27], }); + idChapters[28].UnlockedUpgrades.AddRange(new List { BingoUpgrades.IDBingoUpgrade[28], }); + + return idChapters; + } +} diff --git a/MHFZ_Overlay/Models/Collections/ChallengeItems.cs b/MHFZ_Overlay/Models/Collections/ChallengeItems.cs new file mode 100644 index 00000000..82330cf4 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/ChallengeItems.cs @@ -0,0 +1,32 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The challenge items list. +/// +public static class ChallengeItems +{ + public static ReadOnlyDictionary IDChallengeItem { get; } = new(new Dictionary + { + { + 0, new ChallengeItem + { + Name = "None", + Description = string.Empty, + ImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + IsStackable = false, + MaxStackSize = 1, + IsUnique = false, + Rarity = 1, + } + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/Challenges.cs b/MHFZ_Overlay/Models/Collections/Challenges.cs index 382c1b88..c6fd7e29 100644 --- a/MHFZ_Overlay/Models/Collections/Challenges.cs +++ b/MHFZ_Overlay/Models/Collections/Challenges.cs @@ -4,15 +4,18 @@ namespace MHFZ_Overlay.Models.Collections; +using System; using System.Collections.Generic; +using System.Collections.ObjectModel; public static class Challenges { - public static List ChallengeList { get; private set; } = new List + public static ReadOnlyDictionary IDChallenge { get; } = new(new Dictionary { { - new Challenge() + 0, new Challenge() { + UnlockDate = DateTime.UnixEpoch, BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", Name = "Bingo", AchievementIDRequired = 214, // Zenny Galore @@ -29,8 +32,9 @@ public static class Challenges } }, { - new Challenge() + 1, new Challenge() { + UnlockDate = DateTime.UnixEpoch, BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", Name = "Gacha", AchievementIDRequired = 212, // Quiz Time! @@ -44,8 +48,9 @@ public static class Challenges } }, { - new Challenge() + 2, new Challenge() { + UnlockDate = DateTime.UnixEpoch, BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", Name = "Zenith Gauntlet", AchievementIDRequired = 405, // Crushing Palms @@ -59,8 +64,9 @@ public static class Challenges } }, { - new Challenge() + 3, new Challenge() { + UnlockDate = DateTime.UnixEpoch, BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", Name = "Solstice Gauntlet", AchievementIDRequired = 404, // The Embodiment of Scarlet Devil @@ -74,8 +80,9 @@ public static class Challenges } }, { - new Challenge() + 4, new Challenge() { + UnlockDate = DateTime.UnixEpoch, BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", Name = "Musou Gauntlet", AchievementIDRequired = 195, // Seriously Thirsty @@ -91,5 +98,22 @@ public static class Challenges This merciless gauntlet demands more than mere strength; it calls for tactical finesse and the will to triumph against insurmountable odds. Only those who can surmount this grueling challenge shall earn the title of Unstoppable, showcasing their prowess to the world.", } }, - }; + { + 5, new Challenge() + { + UnlockDate = DateTime.UnixEpoch, + BannerImageLink = @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/achievement/unknown_black.jpg", + Name = "Sky Corridor", + AchievementIDRequired = 223, // Chilling Monster Count + AchievementNameRequired = Achievements.IDAchievement[223].Title, + AchievementsBronzeRequired = 0, + AchievementsSilverRequired = 0, + AchievementsGoldRequired = 0, + AchievementsPlatinumRequired = 0, + ChallengeDataTemplateKey = null, + Description = +@"CURRENTLY UNAVAILABLE. You are tasked with progressing through the Sky Corridor, where puzzles and many dangers are present. There are rumors of there being a guardian in certain floors of the Tower, but no one knows what awaits at the top...", + } + }, + }); } diff --git a/MHFZ_Overlay/Models/Collections/FrontierRarityColors.cs b/MHFZ_Overlay/Models/Collections/FrontierRarityColors.cs new file mode 100644 index 00000000..a69edda7 --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/FrontierRarityColors.cs @@ -0,0 +1,57 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The colors depending on item rarity. +/// +public static class FrontierRarityColors +{ + public static ReadOnlyDictionary RarityColors { get; } = new(new Dictionary + { + { + 1, "#efefe9" + }, + { + 2, "#efefe9" + }, + { + 3, "#efefe9" + }, + { + 4, "#73cb8d" + }, + { + 5, "#ed93a4" + }, + { + 6, "#96b5fd" + }, + { + 7, "#ff985d" + }, + { + 8, "#fffd2e" + }, + { + 9, "#c8ff6a" + }, + { + 10, "#68ecec" + }, + { + 11, "#cba6fa" + }, + { + 12, "#ff435d" + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/FrontierSharpnessColors.cs b/MHFZ_Overlay/Models/Collections/FrontierSharpnessColors.cs new file mode 100644 index 00000000..58236bdf --- /dev/null +++ b/MHFZ_Overlay/Models/Collections/FrontierSharpnessColors.cs @@ -0,0 +1,45 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Collections; + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Security.Cryptography; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; + +/// +/// The colors depending on sharpness. +/// +public static class FrontierSharpnessColors +{ + public static ReadOnlyDictionary SharpnessColors { get; } = new(new Dictionary + { + { + FrontierSharpness.Red, "#c50f3a" + }, + { + FrontierSharpness.Orange, "#e85218" + }, + { + FrontierSharpness.Yellow, "#f3c832" + }, + { + FrontierSharpness.Green, "#5ed300" + }, + { + FrontierSharpness.Blue, "#3068ee" + }, + { + FrontierSharpness.White, "#f0f0f0" + }, + { + FrontierSharpness.Purple, "#de7aff" + }, + { + FrontierSharpness.Cyan, "#86f4f4" + }, + }); +} diff --git a/MHFZ_Overlay/Models/Collections/Players.cs b/MHFZ_Overlay/Models/Collections/Players.cs index 8e43ea35..2a176f36 100644 --- a/MHFZ_Overlay/Models/Collections/Players.cs +++ b/MHFZ_Overlay/Models/Collections/Players.cs @@ -7,7 +7,9 @@ namespace MHFZ_Overlay.Models.Collections; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Data.SQLite; using System.Globalization; +using System.Windows.Documents; /// /// The players list. @@ -17,9 +19,9 @@ public static class Players public static ReadOnlyDictionary> PlayerIDs { get; } = new (new Dictionary> { // No Player - { 0, new List { DateTime.UnixEpoch.Date.ToString(CultureInfo.InvariantCulture), "None", "NoGuild", "0", "Unknown", "Japan" } }, + { 0, new List { new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"), "None", "NoGuild", "0", "Unknown", "Japan" } }, // Local Player - { 1, new List { DateTime.UtcNow.Date.ToString(CultureInfo.InvariantCulture), "HunterName", "GuildName", "0", "Unknown", "Japan" } }, + { 1, new List { DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"), "HunterName", "GuildName", "0", "Unknown", "Japan" } }, }); } diff --git a/MHFZ_Overlay/Models/Constant/Numbers.cs b/MHFZ_Overlay/Models/Constant/Numbers.cs index f1c9b85b..75c35092 100644 --- a/MHFZ_Overlay/Models/Constant/Numbers.cs +++ b/MHFZ_Overlay/Models/Constant/Numbers.cs @@ -9,11 +9,11 @@ public static class Numbers /// /// The frames per second in the game. /// - public const int FramesPerSecond = 30; + public const decimal FramesPerSecond = 30; - public const int Frames1Minute = FramesPerSecond * 60; + public const decimal Frames1Minute = FramesPerSecond * 60; - public const int Frames1Hour = Frames1Minute * 60; + public const decimal Frames1Hour = Frames1Minute * 60; public const int RequiredCompletionsMonsterSlayer = 10; @@ -218,6 +218,8 @@ TODO replace the numbers in source code as necessary public const int QuestIDMultiplayerRoad = 23527; + public const int QuestIDFirstDistrictDuremudira = 21731; + public const int QuestIDSecondDistrictDuremudira = 21746; public const int QuestIDTwinheadRajangsHistoric = 55937; @@ -233,4 +235,30 @@ TODO replace the numbers in source code as necessary public const int QuestIDHalloweenSpeedster = 53325; public const int QuestIDVR = 53232; + + public const int MezFesSRankNyanrendo = 10340; + + public const int MezFesSRankDokkanBattleCats = 110750; + + public const int MezFesSRankPanicHoney = 100; + + public const int MezFesSRankGuukuScoop = 106980; + + /// + /// Not musou. + /// + public const int DuremudiraTimeLimitMinutes = 20; + + public const int ArrogantDuremudiraTimeLimitMinutes = 10; + + /// + /// TODO: This should be replaced when finding timedefint for dure quests. Not musou. + /// + public const decimal DuremudiraTimeLimitFrames = 36_000M; + + public const int AshenLaoQuestID = 21699; + + public const int HR3ShenGaoren = 21696; + + public const int HR3YamaTsukami = 26298; } diff --git a/MHFZ_Overlay/Models/Constant/TimeFormats.cs b/MHFZ_Overlay/Models/Constant/TimeFormats.cs index a720f904..5fcb0277 100644 --- a/MHFZ_Overlay/Models/Constant/TimeFormats.cs +++ b/MHFZ_Overlay/Models/Constant/TimeFormats.cs @@ -9,7 +9,9 @@ namespace MHFZ_Overlay.Models.Constant; /// public static class TimeFormats { + // TODO this caps public const string HoursMinutesSecondsMilliseconds = @"hh\:mm\:ss\.fff"; + // TODO this caps public const string MinutesSecondsMilliseconds = @"mm\:ss\.fff"; } diff --git a/MHFZ_Overlay/Models/LevelProgressionExponential.cs b/MHFZ_Overlay/Models/LevelProgressionExponential.cs new file mode 100644 index 00000000..feae91f9 --- /dev/null +++ b/MHFZ_Overlay/Models/LevelProgressionExponential.cs @@ -0,0 +1,52 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +/// +/// The rate of change increases or decreases exponentially, often leading to rapid growth or decay. +/// +public sealed class LevelProgressionExponential +{ + /// + /// The initial value of the first level. + /// + public decimal InitialValue { get; set; } + + /// + /// The exponential increase of the value. This factor determines how much the value increases with each level. For example, if ValueIncreaseFactor is 1.5, then the value will increase by 50% with each level. + /// + public decimal ValueIncreaseFactor { get; set; } + + /// + /// Calculates the value for that particular level. + /// + /// + /// The calculated value. + public decimal CalculateExponentialValueForLevel(int level) + { + return (decimal)Math.Ceiling(InitialValue * (decimal)Math.Pow((double)ValueIncreaseFactor, level - 1)); + } + + /// + /// Assumes the initial level starts at 1. + /// + /// + /// The cumulative value at max level. + public decimal CalculateCumulativeValueForMaxLevel(int maxLevel) + { + var cumulativeValue = 0M; + for (int level = 1; level <= maxLevel; level++) + { + cumulativeValue += CalculateExponentialValueForLevel(level); + } + return cumulativeValue; + } +} diff --git a/MHFZ_Overlay/Models/LevelProgressionLinear.cs b/MHFZ_Overlay/Models/LevelProgressionLinear.cs new file mode 100644 index 00000000..2dc23fcd --- /dev/null +++ b/MHFZ_Overlay/Models/LevelProgressionLinear.cs @@ -0,0 +1,46 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +/// +/// The rate of change is constant. It forms a straight line on a graph. +/// +public sealed class LevelProgressionLinear +{ + /// + /// The initial value of the first level. + /// + public decimal InitialValue { get; set; } + + /// + /// The linear increase of the values. This factor determines how much the value increases with each level. For example, if ValueIncreasePerLevel is 10, then the value will increase by 10 with each level. + /// + public decimal ValueIncreasePerLevel { get; set; } + + /// + /// Calculate the value based on the level. + /// + /// + /// + public decimal CalculateLinearValueForLevel(int level) + { + return InitialValue + (ValueIncreasePerLevel * (level - 1)); + } + + /// + /// Assumes the initial level starts at 1. + /// + /// + /// The cumulative value at max level. + public decimal CalculateCumulativeValueForMaxLevel(int maxLevel) + { + var cumulativeValue = 0M; + for (int level = 1; level <= maxLevel; level++) + { + cumulativeValue += CalculateLinearValueForLevel(level); + } + return cumulativeValue; + } +} diff --git a/MHFZ_Overlay/Models/LevelProgressionPower.cs b/MHFZ_Overlay/Models/LevelProgressionPower.cs new file mode 100644 index 00000000..518a2c77 --- /dev/null +++ b/MHFZ_Overlay/Models/LevelProgressionPower.cs @@ -0,0 +1,54 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +/// +/// The rate of change follows a power function, where it's raised to a constant exponent. +/// +public sealed class LevelProgressionPower +{ + /// + /// The initial value of the first level. + /// + public decimal InitialValue { get; set; } + + /// + /// The exponent that determines the rate of change. This factor determines how the value changes with each level. For example, if Exponent is 2, then the value will increase squared with each level. + /// + public decimal Exponent { get; set; } + + /// + /// Calculate the value based on the level. + /// + /// + /// The calculated value. + public decimal CalculatePowerValueForLevel(int level) + { + return (decimal)Math.Ceiling(InitialValue * (decimal)Math.Pow(level, (double)Exponent)); + } + + /// + /// Assumes the initial level starts at 1. + /// + /// + /// The cumulative value at max level. + public decimal CalculateCumulativeValueForMaxLevel(int maxLevel) + { + var cumulativeValue = 0M; + for (int level = 1; level <= maxLevel; level++) + { + cumulativeValue += CalculatePowerValueForLevel(level); + } + return cumulativeValue; + } +} + +// POWER LEVELS diff --git a/MHFZ_Overlay/Models/Messengers/QuestIDMessage.cs b/MHFZ_Overlay/Models/Messengers/QuestIDMessage.cs new file mode 100644 index 00000000..804552cf --- /dev/null +++ b/MHFZ_Overlay/Models/Messengers/QuestIDMessage.cs @@ -0,0 +1,19 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Messengers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging.Messages; + +public class QuestIDMessage : ValueChangedMessage +{ + public QuestIDMessage(int value) : base(value) + { + } +} diff --git a/MHFZ_Overlay/Models/Messengers/RunIDMessage.cs b/MHFZ_Overlay/Models/Messengers/RunIDMessage.cs new file mode 100644 index 00000000..f30cdc50 --- /dev/null +++ b/MHFZ_Overlay/Models/Messengers/RunIDMessage.cs @@ -0,0 +1,19 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models.Messengers; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.Messaging.Messages; + +public class RunIDMessage : ValueChangedMessage +{ + public RunIDMessage(int value) : base(value) + { + } +} diff --git a/MHFZ_Overlay/Models/QuestsToggleMode.cs b/MHFZ_Overlay/Models/QuestsToggleMode.cs new file mode 100644 index 00000000..245b9d7e --- /dev/null +++ b/MHFZ_Overlay/Models/QuestsToggleMode.cs @@ -0,0 +1,17 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Models; + +using System; + +// TODO: ORM +public sealed class QuestsToggleMode +{ + public long? QuestsToggleModeID { get; set; } + + public long? QuestToggleMode { get; set; } + + public long? RunID { get; set; } +} diff --git a/MHFZ_Overlay/Models/Structures/Bitfields.cs b/MHFZ_Overlay/Models/Structures/Bitfields.cs index 1594ee8f..ee0fe56a 100644 --- a/MHFZ_Overlay/Models/Structures/Bitfields.cs +++ b/MHFZ_Overlay/Models/Structures/Bitfields.cs @@ -191,5 +191,203 @@ public enum QuestState : uint UNK5 = 32, UNK6 = 64, UNK7 = 128, - QuestClear = AchievedMainObjective | UNK7, + RewardScreen = AchievedMainObjective | UNK7, +} + +/// +/// Quest banned weapons. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestBannedWeapons : uint +{ + [DefaultValue(None)] + None = 0, + Tower = 1, + Evolution = 2, + Master = 4, + HC = 8, + SP = 16, + RNGou = 32, + Gou = 64, + Heaven = 128, + Supremacy = 256, + GSupremacy = 512, + Burst = 1024, + GRank = 2048, + GLevel = 4096, + Origin = 8192, + Other = 16384, + Exotic = 32768, + Prayer = 65536, + Zenith = 131072, +} + +/// +/// Quest weapon types disabled. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestWeaponTypesDisabled : uint +{ + [DefaultValue(None)] + None = 0, + GreatSword = 1, + HeavyBowgun = 2, + Hammer = 4, + Lance = 8, + SwordAndShield = 16, + LightBowgun = 32, + DualSwords = 64, + LongSword = 128, + HuntingHorn = 256, + Gunlance = 512, + Bow = 1024, + Tonfa = 2048, + SwitchAxeF = 4096, + // MS Flag 64 +} + +/// +/// Quest variant 1. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestVariant1 : uint +{ + [DefaultValue(HR)] + HR = 0, + Hiden = 1, + HardcoreFixed = 2, + HardcoreUnlimitedToggle = 4, + GRank = 8, + UNK1 = 16, + Diva = 32, + HarcoreNormalToggle = 64, + UnlimitedFixed = 128, +} + +/// +/// Quest variant 2. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestVariant2 : uint +{ + [DefaultValue(None)] + None = 0, + Level1 = 1, + DisableHalkPotion = 2, + DisableHalkPoogie = 4, + Timer = 8, + DisableActiveFeature = 16, + FixedDifficulty = 32, + Level9999 = 64, + Road = 128, +} + +/// +/// Quest variant 3. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestVariant3 : uint +{ + [DefaultValue(None)] + None = 0, + DisableRewardBonus = 1, + RequireGRank = 2, + UNK1 = 4, + UNK2 = 8, + Zenith = 16, + DivaDefense = 32, + UNK3Course = 64, + DisabledSigil = 128, +} + +/// +/// Quest variant 4. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestVariant4 : uint +{ + [DefaultValue(None)] + None = 0, + UNK0 = 1, + UNK1 = 2, + UNK2 = 4, + UNK3 = 8, + UNK4 = 16, + UNK5 = 32, + UNK6 = 64, + UNK7 = 128, +} + +/// +/// Quest objective type. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestObjectiveType : uint +{ + [DefaultValue (None)] + None = 0, + Hunt = 1, + Deliver = 2, + UNK1 = 4, + UNK2 = 8, + + /// + /// TODO: what is this for? + /// + EsotericAction = 16, + UNK3 = 32, + UNK4 = 64, + UNK5 = 128, + UNK6 = 256, + Capture = Hunt | UNK6, + UNK7 = 512, + Slay = Hunt | UNK7, + UNK8 = 1_024, + UNK9 = 2_048, + UNK10 = 4_096, + DeliverFlag = Deliver | UNK10, + UNK11 = 8192, + UNK12 = 16_384, + PartBreak = UNK1 | UNK12, + UNK13 = 32_768, + Damage = UNK1 | UNK13, + UNK14 = 65_536, + SlayDamage = UNK14 | UNK13 | UNK1, + SlayTotal = 131_072, + SlayAll = 262_144, +} + +/// +/// Quest toggle monster mode option. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum QuestToggleMonsterModeOption : uint +{ + [DefaultValue(Normal)] + Normal = 0, + Hardcore = 1, + UNK1 = 2, + Unlimited = Hardcore | UNK1, +} + +/// +/// Gauntlet Boost for Bingo. +/// +[Flags] +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum GauntletBoost : uint +{ + [DefaultValue(None)] + None = 0, + Zenith = 1, + Solstice = 2, + Musou = 4, } diff --git a/MHFZ_Overlay/Models/Structures/Enums.cs b/MHFZ_Overlay/Models/Structures/Enums.cs index e4d3e83f..dbe46e97 100644 --- a/MHFZ_Overlay/Models/Structures/Enums.cs +++ b/MHFZ_Overlay/Models/Structures/Enums.cs @@ -4,6 +4,8 @@ namespace MHFZ_Overlay.Models.Structures; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text.Json.Serialization; [JsonConverter(typeof(JsonStringEnumConverter))] @@ -76,7 +78,7 @@ public enum GetterMode /// /// Gets a single object/primitive /// - Single, + One, /// /// Gets the count of the object @@ -166,3 +168,350 @@ public enum SetterMode /// ClearAll, } + +public enum ChallengeState +{ + /// + /// The challenge is available for start + /// + Idle, + + /// + /// The challenge is currently in progress, other challenges cannot be running and must be idle. + /// + Running, +} + +/// +/// The category of the bingo or gauntlet challenges. +/// +public enum BingoGauntletCategory +{ + Unknown, + GreatSword, + LongSword, + DualSwords, + SwordAndShield, + Hammer, + HuntingHorn, + Lance, + Gunlance, + LightBowgun, + HeavyBowgun, + Bow, + Tonfa, + SwitchAxeF, + MagnetSpike, + + /// + /// If you used more than 1 weapon type in any quests during a bingo/gauntlet. + /// + Multiple, + + /// + /// If the party size was greater than 1 in any quests during a bingo/gauntlet. Overrides Multiple. + /// + Multiplayer, +} + +/// +/// The type of bingo upgrade. +/// +public enum BingoUpgradeType +{ + /// + /// The flat increase to the base score before multiplying by BaseScoreMultiplier. + /// + BaseScoreFlatIncrease, + + /// + /// The multiplier for the base score. + /// + BaseScoreMultiplier, + + /// + /// The multiplier for using a certain weapon type. + /// + WeaponMultiplier, + + /// + /// The score varying by the amount of carts. + /// + CartsScore, + + /// + /// The flat increase to the score, as final calculation, unaffected by any multiplier. + /// + BonusScore, + + /// + /// The multiplier for the middle square being completed. + /// + MiddleSquareMultiplier, + + /// + /// The amount of extra carts when starting a bingo run. + /// + ExtraCarts, + + /// + /// The reduction for the price of starting a bingo depending on difficulty. + /// + StartingCostReduction, + + /// + /// The chance for the middle square in an extreme difficulty bingo to be rerolled. + /// + MiddleSquareRerollChance, + + /// + /// The amount of times an extreme difficulty bingo board can be rerolled so that Burning Freezing Elzelion can show on a random cell, depending on BurningFreezingElzelionRerollChance. + /// + BurningFreezingElzelionRerolls, + + /// + /// The chance for a Burning Freezing Elzelion to show in a random cell if rerolled. + /// + BurningFreezingElzelionRerollChance, + + /// + /// The multiplier with value varying by the amount of obtained achievements, except secret achievements. + /// + AchievementMultiplier, + + /// + /// The score varying by the amount of obtained secret achievements. + /// + SecretAchievementMultiplier, + + /// + /// The multiplier with value varying by the amount of bingo runs completed (multiplies the bingo run completions by this multiplier then does log 2). + /// + BingoCompletionsMultiplier, + + /// + /// The multiplier for the square completed containing a zenith monster. + /// + ZenithMultiplier, + + /// + /// The multiplier for the square completed containing a conquest or shiten monster. + /// + SolsticeMultiplier, + + /// + /// The multiplier for the square completed containing a musou monster. + /// + MusouMultiplier, + + /// + /// The multiplier for the bingo run being completed on an horizontal line. + /// + HorizontalLineCompletionMultiplier, + + /// + /// The multiplier for the bingo run being completed on a vertical line. + /// + VerticalLineCompletionMultiplier, + + /// + /// The multiplier for the bingo run being completed on a diagonal line. + /// + DiagonalLineCompletionMultiplier, + + /// + /// The multiplier for the time it took to complete the bingo from start button press to the moment the last square is completed on a line. Affected by certain multipliers, calculated right before BonusScore. + /// + RealTimeMultiplier, + + /// + /// The chance of finding a page in a cell at board generation. + /// + PageFinderChance, + + /// + /// The chance of finding an ancient dragon part's scraps in a cell at board generation. + /// + AncientDragonPartScrapChance, + + /// + /// The rate for the compound interest of the bingo points. It compounds x times where x is the amount of bingo cells completed in a run. + /// + BingoPointsCompoundInterestRate, + + /// + /// The cost reduction for the bingo shop upgrades. + /// + BingoShopUpgradeReduction, + + /// + /// The rate/speed at which the transcend meter fills. + /// + TranscendMeterFillRate, + + /// + /// The cost reduction of a true transcend (essentially lowers the meter capacity, which might be a drawback for using normal transcend). + /// + TrueTranscendCostReduction, + + /// + /// Decreases the rate at which the transcend meter drains. + /// + TranscendMeterDrainReduction, + + /// + /// Increases the grace period for obtaining the maximum time score. + /// + MaxTimeScoreGracePeriodIncrease, + + /// + /// Unlocks the book of secrets tab. + /// + BookOfSecretsTomeOne, +} + +public enum FrontierWeaponType +{ + GreatSword, + HeavyBowgun, + Hammer, + Lance, + SwordAndShield, + LightBowgun, + DualSwords, + LongSword, + HuntingHorn, + Gunlance, + Bow, + Tonfa, + SwitchAxeF, + MagnetSpike, +} + +public enum BingoLineColorOption +{ + /// + /// The board will mark blue the run that gives the most points. + /// + Hardest, + + /// + /// The board will mark blue the run that gives the least points. + /// + Easiest, +} + +public enum BingoSquareMonsterType +{ + /// + /// The default monster type. + /// + Default, + + /// + /// The monster is a zenith. + /// + Zenith, + + /// + /// The monster is a conquest or shiten monster. + /// + Solstice, + + /// + /// The monster is a musou. + /// + Musou, +} + +public enum BingoLineCompletionType +{ + /// + /// The line is not known. + /// + Unknown, + + /// + /// The bingo run was completed with a horizontal line. + /// + Horizontal, + + /// + /// The bingo run was completed with a vertical line. + /// + Vertical, + + /// + /// The bingo run was completed with a diagonal line. + /// + Diagonal, +} + +// TODO enums for settings +public enum TimerMode +{ + TimeLeft, + Elapsed, +} + +public enum TimerFormat +{ + MinutesSeconds, + MinutesSecondsMilliseconds, + HoursMinutesSeconds, +} + +public enum OverlayMode +{ + Unknown, + Standard, + Configuring, + ClosedGame, + Launcher, + NoGame, + MainMenu, + WorldSelect, + TimeAttack, + FreestyleSecretTech, + Freestyle, + Zen, +} + +public enum ConfigurationPreset +{ + None, + Speedrun, + Zen, + HPOnly, + All, +} + +public enum FrontierSharpness +{ + Red, + Orange, + Yellow, + Green, + Blue, + White, + Purple, + Cyan, +} + +public enum FrontierMonsterType +{ + /// + /// ???, Unclassified, Unknown, etc. + /// + Other, + + ElderDragon, + Carapaceon, + FlyingWyvern, + BruteWyvern, + PiscineWyvern, + BirdWyvern, + FangedBeast, + Leviathan, + FangedWyvern, +} diff --git a/MHFZ_Overlay/Services/AchievementService.cs b/MHFZ_Overlay/Services/AchievementService.cs index 6d04662e..9c929ecc 100644 --- a/MHFZ_Overlay/Services/AchievementService.cs +++ b/MHFZ_Overlay/Services/AchievementService.cs @@ -8,6 +8,7 @@ namespace MHFZ_Overlay.Services; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Media; @@ -26,6 +27,7 @@ namespace MHFZ_Overlay.Services; public sealed class AchievementService : IAchievementService { private static readonly DatabaseService DatabaseManagerInstance = DatabaseService.GetInstance(); + private static readonly AudioService AudioServiceInstance = AudioService.GetInstance(); public static TimeSpan SnackbarTimeOut { get; set; } = TimeSpan.FromSeconds(5); @@ -46,7 +48,9 @@ public static List FilterAchievementsToCompletedOnly(List achievementsID, Style style) { - MainWindow.MainWindowSoundPlayer?.Play(); + var s = (Settings)Application.Current.TryFindResource("Settings"); + var fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Assets\Sounds\victory.wav"); + AudioServiceInstance.Play(fileName, MainWindow.MainWindowMediaPlayer, s.VolumeMain, s.VolumeAchievementUnlock); const int maxAchievementsToShow = 5; var remainingAchievements = achievementsID.Count - maxAchievementsToShow; @@ -210,7 +214,7 @@ private static bool CheckConditionsForAchievement(int achievementID, DataLoader { default: { - LoggerInstance.Error("Achievement ID {0} not found", achievementID); + LoggerInstance.Error(CultureInfo.InvariantCulture, "Achievement ID {0} not found", achievementID); return false; } @@ -1775,7 +1779,7 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p return false; } - case 215: // TODO test + case 215: if (dataLoader.Model.DivaBond() >= 999) { return true; @@ -1785,9 +1789,14 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p return false; } - case 216: // TODO Obtain S Rank in all single-player MezFes minigames + case 216: { - return false; + return databaseManagerInstance.AllMezFes.Any(minigame => + (minigame.Score >= Numbers.MezFesSRankGuukuScoop && minigame.MezFesMinigameID == 466) && + (minigame.Score >= Numbers.MezFesSRankNyanrendo && minigame.MezFesMinigameID == 467) && + (minigame.Score >= Numbers.MezFesSRankPanicHoney && minigame.MezFesMinigameID == 468) && + (minigame.Score >= Numbers.MezFesSRankDokkanBattleCats && minigame.MezFesMinigameID == 469) + ); } case 217: @@ -1835,7 +1844,7 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p case 222: return databaseManagerInstance.AllPersonalBestAttempts.Any(pbAttempts => pbAttempts.Attempts >= 100); case 223: - if (dataLoader.Model.SecondDistrictDuremudiraSlays() >= 100) + if (dataLoader.Model.SecondDistrictDuremudiraSlays() >= 25) { return true; } @@ -2225,10 +2234,10 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p return false; // Handle invalid TotalTimeElapsed values }); - case 340: // TODO discord rich presence + case 340: return s.EnableRichPresence; case 341: - if (dataLoader.Model.GetOverlayMode().Contains("Zen")) + if (dataLoader.Model.GetOverlayMode() == OverlayMode.Zen) { return true; } @@ -2238,7 +2247,7 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p } case 342: - if (dataLoader.Model.GetOverlayMode().Contains("Freestyle")) + if (dataLoader.Model.GetOverlayMode() is OverlayMode.Freestyle or OverlayMode.FreestyleSecretTech ) { return true; } @@ -2377,7 +2386,7 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p return false; } - case 361: // TODO gacha stuff + case 361: // TODO challenges stuff case 362: case 363: case 364: @@ -2420,12 +2429,223 @@ join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals p case 401: case 402: case 403: + return false; case 404: + completedQuests = from quest in databaseManagerInstance.AllQuests + join activeSkills in databaseManagerInstance.AllActiveSkills on quest.RunID equals activeSkills.RunID + where quest.QuestID == Numbers.QuestIDLV9999CrimsonFatalis && quest.PartySize == 1 && + !(activeSkills.ActiveSkill1ID == 193 || activeSkills.ActiveSkill1ID == 194 || + activeSkills.ActiveSkill2ID == 193 || activeSkills.ActiveSkill2ID == 194 || + activeSkills.ActiveSkill3ID == 193 || activeSkills.ActiveSkill3ID == 194 || + activeSkills.ActiveSkill4ID == 193 || activeSkills.ActiveSkill4ID == 194 || + activeSkills.ActiveSkill5ID == 193 || activeSkills.ActiveSkill5ID == 194 || + activeSkills.ActiveSkill6ID == 193 || activeSkills.ActiveSkill6ID == 194 || + activeSkills.ActiveSkill7ID == 193 || activeSkills.ActiveSkill7ID == 194 || + activeSkills.ActiveSkill8ID == 193 || activeSkills.ActiveSkill8ID == 194 || + activeSkills.ActiveSkill9ID == 193 || activeSkills.ActiveSkill9ID == 194 || + activeSkills.ActiveSkill10ID == 193 || activeSkills.ActiveSkill10ID == 194 || + activeSkills.ActiveSkill11ID == 193 || activeSkills.ActiveSkill11ID == 194 || + activeSkills.ActiveSkill12ID == 193 || activeSkills.ActiveSkill12ID == 194 || + activeSkills.ActiveSkill13ID == 193 || activeSkills.ActiveSkill13ID == 194 || + activeSkills.ActiveSkill14ID == 193 || activeSkills.ActiveSkill14ID == 194 || + activeSkills.ActiveSkill15ID == 193 || activeSkills.ActiveSkill15ID == 194 || + activeSkills.ActiveSkill16ID == 193 || activeSkills.ActiveSkill16ID == 194 || + activeSkills.ActiveSkill17ID == 193 || activeSkills.ActiveSkill17ID == 194 || + activeSkills.ActiveSkill18ID == 193 || activeSkills.ActiveSkill18ID == 194 || + activeSkills.ActiveSkill19ID == 193 || activeSkills.ActiveSkill19ID == 194) + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } case 405: + completedQuests = from quest in databaseManagerInstance.AllQuests + join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals playerGear.RunID + where quest.QuestID == Numbers.QuestIDZ4Gasurabazura && quest.PartySize == 1 && playerGear.PlayerInventoryDictionary != null && + !(JsonConvert.DeserializeObject>>>(playerGear.PlayerInventoryDictionary)?.Values + .SelectMany(list => list) + .Any(innerDict => innerDict.ContainsKey(13607)) ?? false) + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } case 406: - { return false; + case 407: // TODO test + completedQuests = from quest in databaseManagerInstance.AllQuests + join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals playerGear.RunID + where quest.QuestID == Numbers.QuestIDUpperShitenUnknown && + quest.PartySize == 1 && + playerGear.PlayerInventoryDictionary != null && + (JsonConvert.DeserializeObject>>>(playerGear.PlayerInventoryDictionary)?.Values + .SelectMany(list => list) + .All(innerDict => innerDict.ContainsKey(0)) ?? false) + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } + case 408: + return false; + case 409: + { + var keyboardCode = new List { "W", "W", "S", "S", "A", "D", "A", "D", "D2", "D1", }; + var gamepadCode = new List { "DPadUp", "DPadUp", "DPadDown", "DPadDown", "DPadLeft", "DPadRight", "DPadLeft", "DPadRight", "B", "A", }; + + var foundData = from quest in databaseManagerInstance.AllQuests + where (quest.KeyStrokesDictionary != null && + JsonConvert.DeserializeObject>(quest.KeyStrokesDictionary) != null && + quest.GamepadInputDictionary != null && + JsonConvert.DeserializeObject>(quest.GamepadInputDictionary) != null) + select quest; + + if (foundData == null) + { + return false; + } + + var foundCode = from quest in databaseManagerInstance.AllQuests + where (JsonConvert.DeserializeObject>(quest.KeyStrokesDictionary)?.Values + .Select((keyValue) => keyValue.Trim()) + .Take(keyboardCode.Count) + .SequenceEqual(keyboardCode) ?? false) + || + (JsonConvert.DeserializeObject>(quest.GamepadInputDictionary)?.Values + .Select((keyValue) => keyValue.Trim()) + .Take(gamepadCode.Count) + .SequenceEqual(gamepadCode) ?? false) + select quest; + + if (foundCode != null && foundCode.Any()) + { + return true; + } + else + { + return false; + } + } + case 410: + { + completedQuests = from quest in databaseManagerInstance.AllQuests + join playerGear in databaseManagerInstance.AllPlayerGear on quest.RunID equals playerGear.RunID + where quest.QuestID == Numbers.QuestIDBlinkingNargacugaForest && + playerGear.BlademasterWeaponID == 14854 + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } } + case 411: + completedQuests = from quest in databaseManagerInstance.AllQuests + join zenithSkills in databaseManagerInstance.AllZenithSkills on quest.RunID equals zenithSkills.RunID + where + (zenithSkills.ZenithSkill1ID == 47 || + zenithSkills.ZenithSkill2ID == 47 || + zenithSkills.ZenithSkill3ID == 47 || + zenithSkills.ZenithSkill4ID == 47 || + zenithSkills.ZenithSkill5ID == 47 || + zenithSkills.ZenithSkill6ID == 47 || + zenithSkills.ZenithSkill7ID == 47) + && + (zenithSkills.ZenithSkill1ID == 10 || + zenithSkills.ZenithSkill2ID == 10 || + zenithSkills.ZenithSkill3ID == 10 || + zenithSkills.ZenithSkill4ID == 10 || + zenithSkills.ZenithSkill5ID == 10 || + zenithSkills.ZenithSkill6ID == 10 || + zenithSkills.ZenithSkill7ID == 10) + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } + case 412: + return dataLoader.Model.AshenLaoShanLungHunted() >= 100; + case 413: + // TODO test + List targetQuestIDs = new List + { + Numbers.AshenLaoQuestID, + Numbers.HR3YamaTsukami, + Numbers.HR3ShenGaoren, + }; + + return targetQuestIDs.All(targetID => + databaseManagerInstance.AllQuests.Any(quest => quest.QuestID == targetID)); + case 414: + completedQuests = from quest in databaseManagerInstance.AllQuests + join styleRankSkills in databaseManagerInstance.AllStyleRankSkills on quest.RunID equals styleRankSkills.RunID + where + (styleRankSkills.StyleRankSkill1ID == 15 || + styleRankSkills.StyleRankSkill2ID == 15) + && quest.QuestID == Numbers.QuestIDArrogantDuremudira + select quest; + if (completedQuests != null && completedQuests.Any()) + { + return true; + } + else + { + return false; + } + case 415: + case 416: + case 417: + case 418: + case 419: + case 420: + case 421: + case 422: + case 423: + case 424: + case 425: + case 426: + case 427: + case 428: + case 429: + case 430: + case 431: + case 432: + case 433: + case 434: + case 435: + case 436: + case 437: + case 438: + case 439: + return false; + case 440: + if (databaseManagerInstance.AllQuestsToggleMode.Count(questsToggleMode => questsToggleMode.QuestToggleMode == 3) >= 100) + { + return true; + } + else + { + return false; + } } } diff --git a/MHFZ_Overlay/Services/AudioService.cs b/MHFZ_Overlay/Services/AudioService.cs new file mode 100644 index 00000000..03a2a50c --- /dev/null +++ b/MHFZ_Overlay/Services/AudioService.cs @@ -0,0 +1,75 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services; + +using System; +using System.Diagnostics; +using System.Windows.Media; +using MHFZ_Overlay.Services.Contracts; +using NLog; +using SharpCompress.Common; + +public sealed class AudioService : IAudio +{ + public static AudioService GetInstance() + { + if (instance == null) + { + LoggerInstance.Debug("Singleton not found, creating instance."); + instance = new AudioService(); + } + + LoggerInstance.Debug("Singleton found, returning instance."); + LoggerInstance.Trace(new StackTrace().ToString()); + return instance; + } + + /// + public bool Play(string fileName, MediaPlayer? mediaPlayer, float mainVolume, float audioVolume = 1) + { + try + { + if (mediaPlayer == null) + { + LoggerInstance.Warn("Could not find media player"); + return false; + } + + mediaPlayer.Open(new Uri(fileName)); + if (mainVolume > 1) + { + mainVolume = 1; + } + + if (audioVolume > 1) + { + audioVolume = 1; + } + + if (mainVolume < 0) + { + mainVolume = 0; + } + + if (audioVolume < 0) + { + audioVolume = 0; + } + + mediaPlayer.Volume = mainVolume * audioVolume; + mediaPlayer.Play(); + return true; + } + catch(Exception ex) + { + LoggerInstance.Error(ex); + return false; + } + } + + private AudioService() => LoggerInstance.Info($"Service initialized"); + private static readonly Logger LoggerInstance = LogManager.GetCurrentClassLogger(); + private static AudioService? instance; +} diff --git a/MHFZ_Overlay/Services/BingoService.cs b/MHFZ_Overlay/Services/BingoService.cs index 44332642..29a05266 100644 --- a/MHFZ_Overlay/Services/BingoService.cs +++ b/MHFZ_Overlay/Services/BingoService.cs @@ -3,6 +3,450 @@ // found in the LICENSE file. namespace MHFZ_Overlay.Services; -internal class BingoService + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Threading; +using LiveChartsCore.Geo; +using MHFZ_Overlay.Models; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; +using NLog; + +public sealed class BingoService { + public static BingoService GetInstance() + { + if (instance == null) + { + LoggerInstance.Debug("Singleton not found, creating instance."); + instance = new BingoService(); + } + + LoggerInstance.Debug("Singleton found, returning instance."); + LoggerInstance.Trace(new StackTrace().ToString()); + return instance; + } + + // TODO database + + /// + public decimal BaseScoreMultiplier { get; set; } + + /// + public decimal BaseScoreFlatIncrease { get; set; } + + /// + public decimal WeaponMultiplier { get; set; } + + /// + public decimal MiddleSquareMultiplier { get; set; } + + /// + public decimal BonusScore { get; set; } + + /// + public decimal CartsScore { get; set; } + + /// + public decimal AchievementMultiplier { get; set; } + + /// + public decimal SecretAchievementFlatIncrease { get; set; } + + /// + public decimal BingoCompletionsMultiplier { get; set; } + + /// + public decimal ZenithMultiplier { get; set; } + + /// + public decimal SolsticeMultiplier { get; set; } + + /// + public decimal MusouMultiplier { get; set; } + + /// + public decimal HorizontalLineCompletionMultiplier { get; set; } + + /// + public decimal VerticalLineCompletionMultiplier { get; set; } + + /// + public decimal DiagonalLineCompletionMultiplier { get; set; } + + /// + public decimal RealTimeMultiplier { get; set; } + + /// + /// Gets the player bingo points. Used for relegation. The data flow is View <-> ViewModel <-> Service <-> Database + /// + /// The player bingo points. + public long GetPlayerBingoPoints() => DatabaseServiceInstance.GetPlayerBingoPoints(); + + /// + /// Spends the bingo points according to the cost. + /// + /// + /// false if could not spend points. + public bool SpendBingoPoints(long cost) + { + var currentPlayerBingoPoints = GetPlayerBingoPoints(); + + if (currentPlayerBingoPoints < cost) + { + return false; + } + + currentPlayerBingoPoints -= cost; + DatabaseServiceInstance.SetPlayerBingoPoints(currentPlayerBingoPoints); + return true; + } + + /// + /// Applies the bingo upgrade. + /// + /// + /// false if the upgrade could not be applied. + public bool ApplyUpgrade(BingoUpgrade upgrade) + { + if (upgrade.CurrentLevel >= upgrade.MaxLevel) + { + return false; + } + + // Calculate the cost for the next level based on cost progression + // TODO optimize + if (upgrade.Type == BingoUpgradeType.MiddleSquareRerollChance) + { + var costProgression = BingoUpgradeCostProgressions.LinearCostProgressions[upgrade.Type]; + var nextLevel = upgrade.CurrentLevel + 1; + var nextCost = costProgression.CalculateLinearValueForLevel(nextLevel); + var playerBingoPoints = GetPlayerBingoPoints(); + + if (playerBingoPoints < nextCost) + { + return false; + } + + playerBingoPoints -= (long)nextCost; + DatabaseServiceInstance.SetPlayerBingoPoints(playerBingoPoints); + upgrade.CurrentLevel++; + ApplyUpgradeValue(upgrade); + return true; + } + else + { + var costProgression = BingoUpgradeCostProgressions.ExponentialCostProgressions[upgrade.Type]; + var nextLevel = upgrade.CurrentLevel + 1; + var nextCost = costProgression.CalculateExponentialValueForLevel(nextLevel); + var playerBingoPoints = GetPlayerBingoPoints(); + + if (playerBingoPoints < nextCost) + { + return false; + } + + playerBingoPoints -= (long)nextCost; + DatabaseServiceInstance.SetPlayerBingoPoints(playerBingoPoints); + upgrade.CurrentLevel++; + ApplyUpgradeValue(upgrade); + return true; + } + } + + /// + /// Calculates the total bingo points obtained in a bingo run. + /// + /// The final score. + public int CalculateBingoRunTotalPoints() + { + // TODO + var baseScore = 0;// + var difficulty = Difficulty.Unknown;// + var carts = 0;// + var obtainedAchievements = 0; + var obtainedSecretAchievements = 0; + var bingoCompletions = 0; + var isMiddleSquare = false; + var weaponBonusActive = false; + + var monsterType = BingoSquareMonsterType.Default; + var monsterTypeMultiplier = 1M; + switch (monsterType) + { + case BingoSquareMonsterType.Zenith: + monsterTypeMultiplier = ZenithMultiplier; + break; + case BingoSquareMonsterType.Solstice: + monsterTypeMultiplier = SolsticeMultiplier; + break; + case BingoSquareMonsterType.Musou: + monsterTypeMultiplier = MusouMultiplier; + break; + } + + var bingoLineType = BingoLineCompletionType.Unknown; + var bingoLineTypeMultiplier = 1M; + switch (bingoLineType) + { + case BingoLineCompletionType.Diagonal: + bingoLineTypeMultiplier = DiagonalLineCompletionMultiplier; + break; + case BingoLineCompletionType.Horizontal: + bingoLineTypeMultiplier = HorizontalLineCompletionMultiplier; + break; + case BingoLineCompletionType.Vertical: + bingoLineTypeMultiplier = VerticalLineCompletionMultiplier; + break; + } + + // TODO this has to change depending on the square state. + // The below function would be called here and at board generation to show the max possible score on the squares. + var squareBingoPoints = CalculateBingoSquarePoints(baseScore, carts, monsterTypeMultiplier, isMiddleSquare, weaponBonusActive); + var squares = 5; + + // TODO number of lines crossing, max 4. + var linesCrossing = 4; + var totalSquaresBingoPoints = squares * squareBingoPoints; + + var extremeDifficultyMultiplier = difficulty == Difficulty.Extreme ? 2 : 1; + var obtainedAchievementsMultiplier = 1 + (AchievementMultiplier * obtainedAchievements); + var obtainedSecretAchievementsScore = SecretAchievementFlatIncrease * obtainedSecretAchievements; + + // TODO + decimal bingoCompletionsLogMultiplier = (decimal)Math.Log2((double)(1 + (bingoCompletions * BingoCompletionsMultiplier))); + + // TODO + var elapsedRealTimeInSeconds = 0; + var elapsedRealTimeInHours = elapsedRealTimeInSeconds / 3600M; + + // TODO + // f(x) = 1/(e^x) where x is the hours elapsed. + var realTimeScoreMultiplier = (1M + (1M / (decimal)Math.Pow(Math.E, (double)elapsedRealTimeInHours)) * (RealTimeMultiplier * extremeDifficultyMultiplier)); // max multi at infinite time tends to 4. max multi is 8. + var maxRealTimeScore = BingoStartCosts.DifficultyCosts[difficulty]; + decimal maxRealTimeScoreSecondsLimit = (decimal)Math.Ceiling(TimeSpan.FromMinutes(10 * extremeDifficultyMultiplier).TotalSeconds); + + decimal realTimeScore = 0; + // TODO maybe remove grace period. or make it an upgrade. + if (elapsedRealTimeInSeconds <= maxRealTimeScoreSecondsLimit) + { + realTimeScore = maxRealTimeScore; + } + else + { + realTimeScore = CalculateRealTimeScore(elapsedRealTimeInSeconds, realTimeScoreMultiplier, maxRealTimeScore, extremeDifficultyMultiplier); + } + + // TODO + // bingo ponts for final score = (total squares bingo points * extreme difficulty multiplier) + + bonus score + // TODO see which one of the 2 affects the score the most (bingoBoardScore vs extraScore) + var bingoBoardScore = totalSquaresBingoPoints * linesCrossing * extremeDifficultyMultiplier; + var extraScore = realTimeScore * bingoCompletionsLogMultiplier * obtainedAchievementsMultiplier; + var finalScore = bingoBoardScore + extraScore + obtainedSecretAchievementsScore + BonusScore; + return (int)Math.Ceiling(finalScore); + } + + public decimal CalculateRealTimeScore(int elapsedRealTimeInSeconds, decimal realTimeScoreMultiplier, int maxRealTimeScore, int extremeDifficultyMultiplier) + { + var maxRealTimeScoreMinuteLimit = 10 * extremeDifficultyMultiplier; + var maxRealTimeScoreLastSecond = TimeSpan.FromMinutes(maxRealTimeScoreMinuteLimit).TotalSeconds; + var fastDecreaseFromMaxScoreLastSecond = TimeSpan.FromMinutes(maxRealTimeScoreMinuteLimit * 1.2).TotalSeconds; + var slowDecreaseFromHalvedScoreLastSecond = TimeSpan.FromMinutes(maxRealTimeScoreMinuteLimit * 1.4).TotalSeconds; + var fastDecreaseFinalLastSecond = TimeSpan.FromMinutes(maxRealTimeScoreMinuteLimit * 1.6).TotalSeconds; + + // Initialize your Bezier curve with the control points + //BezierCurve curve = new BezierCurve( + // new Vector2((float)maxRealTimeScoreLastSecond, maxRealTimeScore), + // new Vector2((float)fastDecreaseFromMaxScoreLastSecond, (float)(maxRealTimeScore * 0.75)), + // new Vector2((float)slowDecreaseFromHalvedScoreLastSecond, (float)(maxRealTimeScore * 0.50)), + // new Vector2((float)fastDecreaseFinalLastSecond, (float)(maxRealTimeScore * 0.25)) + //); + + BezierCurve curve = new BezierCurve( + new Vector2(2 * 60 * 60, 0), + new Vector2(2 * 60 * 60, 1000), + new Vector2(10 * 60, 0), + new Vector2(10 * 60, 1000) + ); + + // Calculate the elapsed time (in seconds) + float elapsedTime = 3000; + + // Calculate the total time (in seconds) + float totalTime = 10000; + + // Calculate the t parameter + float t = Math.Min(elapsedTime / totalTime, 1); + + // Calculate the score + Vector2 scorePoint = curve.Evaluate(t); + var score = scorePoint.Y; + + return (decimal)score; + } + + /// + /// Calculates the bingo points obtained on the square. + /// + /// + /// The bingo points obtained. + public decimal CalculateBingoSquarePoints(int baseScore, int carts, decimal monsterTypeMultiplier, bool isMiddleSquare, bool weaponBonusActive) + { + // bingo points for a square = ((((base score + base score flat increase) * base score multiplier) + total carts score) * weapon multiplier * middle square multiplier) + var cartsPenalty = 1M; + switch (carts) + { + case 1: + cartsPenalty = 2M; + break; + case >= 2: + cartsPenalty = 3M; + break; + } + var totalCartsScore = CartsScore / cartsPenalty; + + var weaponMultiplier = weaponBonusActive ? WeaponMultiplier : 1; + var middleSquareMultiplier = isMiddleSquare ? MiddleSquareMultiplier : 1; + var score = + ( + ( + ( + (baseScore + BaseScoreFlatIncrease) + * BaseScoreMultiplier + ) + totalCartsScore + ) * weaponMultiplier * middleSquareMultiplier * monsterTypeMultiplier + ); + return score; + } + + /// + /// Updates the upgrades values with the level value increase. + /// + /// + public void ApplyUpgradeValue(BingoUpgrade upgrade) + { + if (BingoUpgradeValueProgressions.ValueProgressions.TryGetValue(upgrade.Type, out var valueProgression)) + { + // Apply the upgrade's effects on the bingo game using the value progression + decimal valueIncrease = valueProgression.CalculateLinearValueForLevel(upgrade.CurrentLevel); + + switch (upgrade.Type) + { + case BingoUpgradeType.BaseScoreFlatIncrease: + BaseScoreFlatIncrease += valueIncrease; + break; + case BingoUpgradeType.BaseScoreMultiplier: + BaseScoreMultiplier += valueIncrease; + break; + case BingoUpgradeType.BonusScore: + BonusScore += valueIncrease; + break; + case BingoUpgradeType.MiddleSquareMultiplier: + MiddleSquareMultiplier += valueIncrease; + break; + case BingoUpgradeType.WeaponMultiplier: + WeaponMultiplier += valueIncrease; + break; + case BingoUpgradeType.CartsScore: + CartsScore += valueIncrease; + break; + } + } + else + { + LoggerInstance.Error(CultureInfo.InvariantCulture, "Could not find value progression for {0}", upgrade.Type); + } + } + + /// + /// Starts a simulation of bingo runs. + /// + /// + public void SimulateBingoRuns(int runs) + { + // TODO statistics + } + + /// + /// TODO upgrades affecting cost. + /// + /// + /// + /// + /// + public int CalculateBingoStartCost(GauntletBoost gauntletBoost, Difficulty difficulty, bool musouElzelionBoost) + { + var cost = 0; + + // Base cost based on difficulty + if (BingoStartCosts.DifficultyCosts.TryGetValue(difficulty, out var difficultyCost)) + { + cost = difficultyCost; + } + + // Additional cost based on gauntlet boost + if (gauntletBoost.HasFlag(GauntletBoost.Zenith)) + { + cost += BingoStartCosts.GauntletBoostCosts[GauntletBoost.Zenith]; + } + + if (gauntletBoost.HasFlag(GauntletBoost.Solstice)) + { + cost += BingoStartCosts.GauntletBoostCosts[GauntletBoost.Solstice]; + } + + if (gauntletBoost.HasFlag(GauntletBoost.Musou)) + { + cost += BingoStartCosts.GauntletBoostCosts[GauntletBoost.Musou]; + } + + // Additional cost based on Musou Elzelion boost + if (musouElzelionBoost) + { + cost += BingoStartCosts.MusouElzelionBoostCost; + } + + // Additional cost based on number of players + // cost += numberOfPlayers * 20; + return cost; + } + + /// + /// Calculates the carts depending on difficulty selected, for bingo start. + /// + /// + /// + public int CalculateCartsAtBingoStartFromSelectedDifficulty(Difficulty difficulty) + { + var carts = 0; + + // Base carts based on difficulty + if (BingoDifficultyCarts.DifficultyCarts.TryGetValue(difficulty, out var difficultyCarts)) + { + carts = difficultyCarts; + } + + return carts; + } + + /// + /// For testing only. TODO add for challenge unlocks. + /// + /// + public long SetPlayerBingoPoints(long points) + { + DatabaseServiceInstance.SetPlayerBingoPoints(points); + return points; + } + + private BingoService() => LoggerInstance.Info($"Service initialized"); + private static readonly Logger LoggerInstance = LogManager.GetCurrentClassLogger(); + private static readonly DatabaseService DatabaseServiceInstance = DatabaseService.GetInstance(); + private static BingoService? instance; } diff --git a/MHFZ_Overlay/Services/ChallengeService.cs b/MHFZ_Overlay/Services/ChallengeService.cs index 953e7a0e..3ede5da2 100644 --- a/MHFZ_Overlay/Services/ChallengeService.cs +++ b/MHFZ_Overlay/Services/ChallengeService.cs @@ -3,6 +3,155 @@ // found in the LICENSE file. namespace MHFZ_Overlay.Services; -internal class ChallengeService + +using System; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using MHFZ_Overlay.Models; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services.Contracts; +using MHFZ_Overlay.Views.Windows; + +public sealed class ChallengeService : IChallengeService { + /// + public ChallengeState State { get; set; } + + public static ChallengeService GetInstance() + { + if (instance == null) + { + Logger.Debug(CultureInfo.InvariantCulture, "Singleton not found, creating instance."); + instance = new ChallengeService(); + } + + Logger.Debug(CultureInfo.InvariantCulture, "Singleton found, returning instance."); + Logger.Trace(CultureInfo.InvariantCulture, new StackTrace().ToString()); + return instance; + } + + public static BingoGauntletCategory ConvertToBingoGauntletCategory(long category) => category switch + { + 0 => BingoGauntletCategory.Unknown, + 1 => BingoGauntletCategory.GreatSword, + 2 => BingoGauntletCategory.LongSword, + 3 => BingoGauntletCategory.DualSwords, + 4 => BingoGauntletCategory.SwordAndShield, + 5 => BingoGauntletCategory.Hammer, + 6 => BingoGauntletCategory.HuntingHorn, + 7 => BingoGauntletCategory.Lance, + 8 => BingoGauntletCategory.Gunlance, + 9 => BingoGauntletCategory.LightBowgun, + 10 => BingoGauntletCategory.HeavyBowgun, + 11 => BingoGauntletCategory.Bow, + 12 => BingoGauntletCategory.Tonfa, + 13 => BingoGauntletCategory.SwitchAxeF, + 14 => BingoGauntletCategory.MagnetSpike, + 15 => BingoGauntletCategory.Multiple, + 16 => BingoGauntletCategory.Multiplayer, + _ => BingoGauntletCategory.Unknown, + }; + + /// + public bool CheckRequirements(Challenge challenge) + { + var playerAchievements = DatabaseServiceInstance.GetPlayerAchievements(); + + var obtainedBronzeAchievements = playerAchievements.Count(a => a.CompletionDate != DateTime.UnixEpoch && a.Rank == AchievementRank.Bronze); + var obtainedSilverAchievements = playerAchievements.Count(a => a.CompletionDate != DateTime.UnixEpoch && a.Rank == AchievementRank.Silver); + var obtainedGoldAchievements = playerAchievements.Count(a => a.CompletionDate != DateTime.UnixEpoch && a.Rank == AchievementRank.Gold); + var obtainedPlatinumAchievements = playerAchievements.Count(a => a.CompletionDate != DateTime.UnixEpoch && a.Rank == AchievementRank.Platinum); + var obtainedSecretAchievement = playerAchievements.Any(achievement => achievement.CompletionDate != DateTime.UnixEpoch && achievement.Title == challenge.AchievementNameRequired && achievement.IsSecret); + + return (obtainedBronzeAchievements >= challenge.AchievementsBronzeRequired && + obtainedSilverAchievements >= challenge.AchievementsSilverRequired && + obtainedGoldAchievements >= challenge.AchievementsGoldRequired && + obtainedBronzeAchievements >= challenge.AchievementsPlatinumRequired && + obtainedSecretAchievement); + } + + /// + public bool Unlock(Challenge challenge) + { + if (!CheckChallengeWindow(challenge)) + { + return false; + } + + // Find the challenge ID by iterating through the ReadOnlyDictionary + foreach (var kvp in Challenges.IDChallenge) + { + if (kvp.Value == challenge) + { + var challengeID = kvp.Key; + return DatabaseServiceInstance.UnlockChallenge(challenge, challengeID); + } + } + + Logger.Error(CultureInfo.InvariantCulture, "Challenge not found in the dictionary, canceling unlock process."); + return false; + } + + /// + public bool Start(Challenge challenge) + { + if (State == ChallengeState.Running) + { + return false; + } + else + { + var success = OpenChallengeWindow(challenge); + + if (success) + { + State = ChallengeState.Running; + } + + return success; + } + } + + /// + public bool OpenChallengeWindow(Challenge challenge) + { + switch (challenge.Name) + { + default: + Logger.Error(CultureInfo.InvariantCulture, "Could not find window for challenge {0}. The challenge has yet to be made by the developer.", challenge.Name); + return false; + case "Bingo": + var window = new BingoWindow(); + window.Show(); + return true; + } + } + + /// + /// Checks if there exists a window for the challenge. + /// + /// + /// False if a window could not be found for the selected challenge. + public bool CheckChallengeWindow(Challenge challenge) + { + switch (challenge.Name) + { + default: + Logger.Info(CultureInfo.InvariantCulture, "Could not find window for challenge {0}. The challenge has yet to be made by the developer.", challenge.Name); + return false; + case "Bingo": + return true; + } + } + + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + private static readonly DatabaseService DatabaseServiceInstance = DatabaseService.GetInstance(); + + private static ChallengeService? instance; + + private ChallengeService() => Logger.Info(CultureInfo.InvariantCulture, $"Service initialized"); } diff --git a/MHFZ_Overlay/Services/Contracts/IAchievement.cs b/MHFZ_Overlay/Services/Contracts/IAchievementService.cs similarity index 100% rename from MHFZ_Overlay/Services/Contracts/IAchievement.cs rename to MHFZ_Overlay/Services/Contracts/IAchievementService.cs diff --git a/MHFZ_Overlay/Services/Contracts/IAudio.cs b/MHFZ_Overlay/Services/Contracts/IAudio.cs new file mode 100644 index 00000000..f35cee3e --- /dev/null +++ b/MHFZ_Overlay/Services/Contracts/IAudio.cs @@ -0,0 +1,23 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Contracts; + +using System.Windows.Media; + +/// +/// https://stackoverflow.com/questions/50521363/soundplayer-adjustable-volume +/// +public interface IAudio +{ + /// + /// Plays the audio file with volume of mainVolume * audioVolume. Maximum volume value in either parameter or the result has to be 1 (100%) at maximum. + /// + /// + /// + /// + /// + /// false if the audio could not be played + bool Play(string fileName, MediaPlayer? mediaplayer, float mainVolume, float audioVolume = 1); +} diff --git a/MHFZ_Overlay/Services/Contracts/IBingo.cs b/MHFZ_Overlay/Services/Contracts/IBingo.cs new file mode 100644 index 00000000..f414c301 --- /dev/null +++ b/MHFZ_Overlay/Services/Contracts/IBingo.cs @@ -0,0 +1,15 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Contracts; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +public interface IBingo +{ +} diff --git a/MHFZ_Overlay/Services/Contracts/IChallengeService.cs b/MHFZ_Overlay/Services/Contracts/IChallengeService.cs new file mode 100644 index 00000000..ceb6f5b8 --- /dev/null +++ b/MHFZ_Overlay/Services/Contracts/IChallengeService.cs @@ -0,0 +1,41 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Contracts; + +using MHFZ_Overlay.Models; +using MHFZ_Overlay.Models.Structures; + +public interface IChallengeService +{ + /// + /// The state of the challenges. + /// + ChallengeState State { get; } + + /// + /// Opens the window for the particular challenge. + /// + /// + /// false if the challenge doesn't have a corresponding window + bool OpenChallengeWindow(Challenge challenge); + + /// + /// Checks the requirements to unlock the challenge. + /// + /// false if the requirements are not met + bool CheckRequirements(Challenge challenge); + + /// + /// Unlocks the challenge. If using a data structure to avoid querying databases, that data structure should also be updated after storing the unlock date with this method. + /// + /// false if the challenge could not be unlocked + bool Unlock(Challenge challenge); + + /// + /// Starts the challenge. If there is already a challenge in progress, that challenge is canceled. + /// + /// false if the challenge could not be started + bool Start(Challenge challenge); +} diff --git a/MHFZ_Overlay/Services/Contracts/IDatabase.cs b/MHFZ_Overlay/Services/Contracts/IDatabase.cs index 9913d264..aa1bd6b7 100644 --- a/MHFZ_Overlay/Services/Contracts/IDatabase.cs +++ b/MHFZ_Overlay/Services/Contracts/IDatabase.cs @@ -22,7 +22,7 @@ public interface IDatabase /// /// /// - object? GetData(object data, GetterMode mode = GetterMode.Single); + object? GetData(object data, GetterMode mode = GetterMode.One); void SetUpDatabase(); } diff --git a/MHFZ_Overlay/Services/Contracts/IDiscordRpcClient.cs b/MHFZ_Overlay/Services/Contracts/IDiscordRpcClient.cs index 394dd6e2..edc0168e 100644 --- a/MHFZ_Overlay/Services/Contracts/IDiscordRpcClient.cs +++ b/MHFZ_Overlay/Services/Contracts/IDiscordRpcClient.cs @@ -72,7 +72,7 @@ public interface IDiscordRpcClient : IDisposable void SetPresence(RichPresence presence); - RichPresence UpdateButtons(Button[]? button = null); + RichPresence UpdateButtons(Button[] ? button = null); RichPresence SetButton(Button button, int index = 0); diff --git a/MHFZ_Overlay/Services/Contracts/ITimeService.cs b/MHFZ_Overlay/Services/Contracts/ITimeService.cs new file mode 100644 index 00000000..fa22a3c4 --- /dev/null +++ b/MHFZ_Overlay/Services/Contracts/ITimeService.cs @@ -0,0 +1,43 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Contracts; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MHFZ_Overlay.Models.Structures; + +public interface ITimeService +{ + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + string GetMinutesSecondsFromSeconds(double seconds); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + string GetMinutesSecondsFromFrames(double frames); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + string GetMinutesSecondsMillisecondsFromFrames(double frames); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + string GetMinutesSecondsMillisecondsFromFrames(long frames); +} diff --git a/MHFZ_Overlay/Services/Converter/BingoMonsterInfoToToolTipConverter.cs b/MHFZ_Overlay/Services/Converter/BingoMonsterInfoToToolTipConverter.cs new file mode 100644 index 00000000..4a355111 --- /dev/null +++ b/MHFZ_Overlay/Services/Converter/BingoMonsterInfoToToolTipConverter.cs @@ -0,0 +1,48 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Converter; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; +using MHFZ_Overlay.Models; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; + +/// +/// Actual info in regards to the bingo points would be in Statistics tab (sandbox). +/// +public class BingoMonsterInfoToToolTipConverter : IValueConverter +{ + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is BingoMonster monster) + { + if (monster.IsUnlimited) + { + return "Any UL non-custom quest"; + } + else + { + if (monster.QuestIDs == null) + { + return string.Empty; + } + + var mapper = EZlion.Mapper.Quest.IDName; + var mappedStrings = monster.QuestIDs.Select(i => mapper.ContainsKey(i) ? mapper[i] : i.ToString()); + return string.Join(" | ", mappedStrings); + } + } + + return string.Empty; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/MHFZ_Overlay/Services/Converter/ConfigurationPresetConverter.cs b/MHFZ_Overlay/Services/Converter/ConfigurationPresetConverter.cs index 80caf8d5..50ee9e24 100644 --- a/MHFZ_Overlay/Services/Converter/ConfigurationPresetConverter.cs +++ b/MHFZ_Overlay/Services/Converter/ConfigurationPresetConverter.cs @@ -4,6 +4,7 @@ namespace MHFZ_Overlay.Services.Converter; +using MHFZ_Overlay.Models.Structures; using static MHFZ_Overlay.Services.OverlaySettingsService; public static class ConfigurationPresetConverter diff --git a/MHFZ_Overlay/Services/Converter/FrontierWeaponTypesToIconsConverter.cs b/MHFZ_Overlay/Services/Converter/FrontierWeaponTypesToIconsConverter.cs new file mode 100644 index 00000000..210f3477 --- /dev/null +++ b/MHFZ_Overlay/Services/Converter/FrontierWeaponTypesToIconsConverter.cs @@ -0,0 +1,45 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Converter; + +using System; +using System.Globalization; +using System.Windows.Data; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; + +public class FrontierWeaponTypeToIconsConverter : IValueConverter +{ + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is FrontierWeaponType type) + { + return type switch + { + FrontierWeaponType.GreatSword => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_gs.png", + FrontierWeaponType.LongSword => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_ls.png", + FrontierWeaponType.DualSwords => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_ds.png", + FrontierWeaponType.SwordAndShield => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_sns.png", + FrontierWeaponType.Hammer => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_hammer.png", + FrontierWeaponType.HuntingHorn => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_hh.png", + FrontierWeaponType.Lance => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_lance.png", + FrontierWeaponType.Gunlance => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_gl.png", + FrontierWeaponType.LightBowgun => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_lbg.png", + FrontierWeaponType.HeavyBowgun => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_hbg.png", + FrontierWeaponType.Bow => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_bow.png ", + FrontierWeaponType.Tonfa => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_tonfa.png", + FrontierWeaponType.SwitchAxeF => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_saf.png", + FrontierWeaponType.MagnetSpike => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/small_ms.png", + _ => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }; + } + + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png"; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/MHFZ_Overlay/Services/Converter/GauntletBoostToImageConverter.cs b/MHFZ_Overlay/Services/Converter/GauntletBoostToImageConverter.cs new file mode 100644 index 00000000..7e6160a8 --- /dev/null +++ b/MHFZ_Overlay/Services/Converter/GauntletBoostToImageConverter.cs @@ -0,0 +1,59 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Converter; + +using System; +using System.Globalization; +using System.Windows.Data; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Structures; + +public class GauntletBoostToImageConverter : IValueConverter +{ + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is GauntletBoost boost) + { + if (boost.HasFlag(GauntletBoost.Zenith) && boost.HasFlag(GauntletBoost.Solstice) && boost.HasFlag(GauntletBoost.Musou)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_max.png"; + } + else if (boost.HasFlag(GauntletBoost.Zenith) && boost.HasFlag(GauntletBoost.Solstice)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_orange.png"; + } + else if (boost.HasFlag(GauntletBoost.Zenith) && boost.HasFlag(GauntletBoost.Musou)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_purple.png"; + } + else if (boost.HasFlag(GauntletBoost.Solstice) && boost.HasFlag(GauntletBoost.Musou)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_cyan.png"; + } + else if (boost.HasFlag(GauntletBoost.Zenith)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_red.png"; + } + else if (boost.HasFlag(GauntletBoost.Solstice)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_green.png"; + } + else if (boost.HasFlag(GauntletBoost.Musou)) + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_blue.png"; + } + else + { + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_white.png"; + } + } + + return @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/gauntlet_white.png"; + } + + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/MHFZ_Overlay/Services/Converter/QuestIDsToNamesConverter.cs b/MHFZ_Overlay/Services/Converter/QuestIDsToNamesConverter.cs new file mode 100644 index 00000000..5ec995be --- /dev/null +++ b/MHFZ_Overlay/Services/Converter/QuestIDsToNamesConverter.cs @@ -0,0 +1,32 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Converter; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +public class QuestIDsToNamesConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is List intList) + { + var mapper = EZlion.Mapper.Quest.IDName; + var mappedStrings = intList.Select(i => mapper.ContainsKey(i) ? mapper[i] : i.ToString()); + return string.Join(" | ", mappedStrings); + } + + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } +} + diff --git a/MHFZ_Overlay/Services/Converter/TextFormattingModeConverter.cs b/MHFZ_Overlay/Services/Converter/TextFormattingModeConverter.cs index 9abe8179..2f91781b 100644 --- a/MHFZ_Overlay/Services/Converter/TextFormattingModeConverter.cs +++ b/MHFZ_Overlay/Services/Converter/TextFormattingModeConverter.cs @@ -30,7 +30,7 @@ public object Convert(object value, Type targetType, object parameter, CultureIn { if (value is TextFormattingMode textFormattingMode) { - return textFormattingMode.ToString(CultureInfo.InvariantCulture); + return textFormattingMode.ToString(); } return null; diff --git a/MHFZ_Overlay/Services/Converter/UnlockChallengeButtonContentConverter.cs b/MHFZ_Overlay/Services/Converter/UnlockChallengeButtonContentConverter.cs new file mode 100644 index 00000000..81aea74e --- /dev/null +++ b/MHFZ_Overlay/Services/Converter/UnlockChallengeButtonContentConverter.cs @@ -0,0 +1,32 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services.Converter; + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Data; + +public class UnlockChallengeButtonContentConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || (value is DateTime unlockDate && unlockDate == DateTime.UnixEpoch)) + { + return "Unlock"; + } + + return "Start"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} + diff --git a/MHFZ_Overlay/Services/DatabaseService.cs b/MHFZ_Overlay/Services/DatabaseService.cs index b2338e46..29358c66 100644 --- a/MHFZ_Overlay/Services/DatabaseService.cs +++ b/MHFZ_Overlay/Services/DatabaseService.cs @@ -13,22 +13,30 @@ namespace MHFZ_Overlay.Services; using System.Globalization; using System.IO; using System.Linq; +using System.Printing; +using System.Runtime.Intrinsics.Arm; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Transactions; using System.Windows; using System.Windows.Controls; +using System.Windows.Documents; using System.Windows.Media.Imaging; +using CommunityToolkit.Mvvm.Messaging; using EZlion.Mapper; using MHFZ_Overlay; using MHFZ_Overlay.Models; using MHFZ_Overlay.Models.Collections; using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Messengers; using MHFZ_Overlay.Models.Structures; using MHFZ_Overlay.Views.Windows; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using NLog; +using Octokit; using Wpf.Ui.Common; using Wpf.Ui.Controls; using Formatting = Newtonsoft.Json.Formatting; @@ -66,6 +74,8 @@ public sealed class DatabaseService public HashSet AllActiveSkills { get; set; } + public HashSet AllZenithSkills { get; set; } + public HashSet AllStyleRankSkills { get; set; } public HashSet AllQuestAttempts { get; set; } @@ -74,17 +84,19 @@ public sealed class DatabaseService public HashSet AllPlayerInventories { get; set; } + public HashSet AllQuestsToggleMode { get; set; } + public TimeSpan SnackbarTimeOut { get; set; } = TimeSpan.FromSeconds(5); - private string? connectionString; + private string? connectionString { get; set; } - private string? customDatabasePath; + private string? customDatabasePath { get; set; } private DatabaseService() => Logger.Info(CultureInfo.InvariantCulture, $"Service initialized"); - private string? dataSource; + private string? dataSource { get; set; } - private bool isDatabaseSetup; + private bool isDatabaseSetup { get; set; } public static DatabaseService GetInstance() { @@ -167,7 +179,7 @@ public TimeSpan CalculateTotalTimeSpent() var result = cmd.ExecuteScalar(); if (result != DBNull.Value) { - totalTimeSpent = TimeSpan.FromSeconds(Convert.ToInt32(result)); + totalTimeSpent = TimeSpan.FromSeconds(Convert.ToInt32(result, CultureInfo.InvariantCulture)); } } @@ -283,6 +295,9 @@ public bool SetupLocalDatabase(DataLoader dataLoader) { conn.Open(); + this.RemoveCurrentDatabaseTriggers(conn); + this.CheckDatesFormat(conn); + // Toggle comment this for testing the error handling // ThrowException(conn); this.CreateDatabaseTables(conn, dataLoader); @@ -429,26 +444,36 @@ public void InsertPersonalBest(DataLoader dataLoader, long currentPersonalBest, /// /// /// - /// - public int InsertQuestData(DataLoader dataLoader, int attempts) + /// the run ID and the quest ID + public (int, int) InsertQuestData(DataLoader dataLoader, int attempts) { Logger.Info(CultureInfo.InvariantCulture, "Inserting quest data"); var runID = 0; + var questID = dataLoader.Model.QuestID(); var actualOverlayMode = string.Empty; dataLoader.Model.ShowSaveIcon = true; + var timeLeft = dataLoader.Model.TimeInt(); + + // TODO dure timedefint address + var timeDefIint = questID != Numbers.QuestIDFirstDistrictDuremudira && questID != Numbers.QuestIDSecondDistrictDuremudira ? dataLoader.Model.TimeDefInt() : Numbers.DuremudiraTimeLimitFrames; + + var finalTimeValue = timeDefIint - timeLeft; + + // Calculate the elapsed time of the quest + var finalTimeDisplay = TimeService.GetMinutesSecondsMillisecondsFromFrames((long)finalTimeValue); var s = (Settings)System.Windows.Application.Current.TryFindResource("Settings"); if (!ViewModels.Windows.AddressModel.ValidateGameFolder() || !s.EnableQuestLogging || !dataLoader.Model.QuestCleared) { - return runID; + return (runID, questID); } if (string.IsNullOrEmpty(this.dataSource)) { Logger.Warn(CultureInfo.InvariantCulture, "Cannot insert quest data. dataSource: {0}", this.dataSource); - return runID; + return (runID, questID); } using (var conn = new SQLiteConnection(this.dataSource)) @@ -470,7 +495,7 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) var result = cmd.ExecuteScalar(); if (result != null && result.ToString() != string.Empty) { - runID = Convert.ToInt32(result); + runID = Convert.ToInt32(result, CultureInfo.InvariantCulture); } else { @@ -583,22 +608,20 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) using (var cmd = new SQLiteCommand(sql, conn)) { - var questID = model.QuestID(); - var timeLeft = model.TimeInt(); // Example value of the TimeLeft variable - var finalTimeValue = model.TimeDefInt() - model.TimeInt(); - - // Calculate the elapsed time of the quest - var finalTimeDisplay = dataLoader.GetQuestTimeCompletion(); - // Convert the elapsed time to a DateTime object string objectiveImage; // Gathering/etc - if ((dataLoader.Model.ObjectiveType() == 0x0 || dataLoader.Model.ObjectiveType() == 0x02 || dataLoader.Model.ObjectiveType() == 0x1002) && dataLoader.Model.QuestID() != 23527 && dataLoader.Model.QuestID() != 23628 && dataLoader.Model.QuestID() != 21731 && dataLoader.Model.QuestID() != 21749 && dataLoader.Model.QuestID() != 21746 && dataLoader.Model.QuestID() != 21750) + if ((dataLoader.Model.ObjectiveType() == 0x0 || dataLoader.Model.ObjectiveType() == 0x02 || dataLoader.Model.ObjectiveType() == 0x1002) && dataLoader.Model.QuestID() != 23527 && dataLoader.Model.QuestID() != 23628 && dataLoader.Model.QuestID() != 21749 && dataLoader.Model.QuestID() != 21750 && dataLoader.Model.QuestID() != Numbers.QuestIDFirstDistrictDuremudira && dataLoader.Model.QuestID() != Numbers.QuestIDSecondDistrictDuremudira) { objectiveImage = ViewModels.Windows.AddressModel.GetAreaIconFromID(dataLoader.Model.AreaID()); } + else if (dataLoader.Model.QuestID() == Numbers.QuestIDSecondDistrictDuremudira || dataLoader.Model.QuestID() == Numbers.QuestIDFirstDistrictDuremudira) + { + objectiveImage = dataLoader.Model.GetMonsterIcon(132); + } + // Tenrou Sky Corridor areas else if (dataLoader.Model.AreaID() is 391 or 392 or 394 or 415 or 416) { @@ -685,8 +708,8 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) // check if its grabbing a TimeDefInt from a previous quest // TODO is this enough? - if ((overlayModeDictionary.Count == 2 && overlayModeDictionary.Last().Value == string.Empty) || - (overlayModeDictionary.Count == 1 && overlayModeDictionary.First().Value == string.Empty) || + if ((overlayModeDictionary.Count == 2 && overlayModeDictionary.Last().Value == "Standard") || + (overlayModeDictionary.Count == 1 && overlayModeDictionary.First().Value == "Standard") || overlayModeDictionary.Count > 2) { actualOverlayMode = "Standard"; @@ -694,7 +717,7 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) else { // TODO: test - if (overlayModeDictionary.Count == 2 && overlayModeDictionary.First().Value == string.Empty) + if (overlayModeDictionary.Count == 2 && overlayModeDictionary.First().Value == "Standard") { actualOverlayMode = overlayModeDictionary.Last().Value; } @@ -703,6 +726,7 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) actualOverlayMode = overlayModeDictionary.First().Value; } + // TODO probably not needed anymore actualOverlayMode = actualOverlayMode.Replace(")", string.Empty); actualOverlayMode = actualOverlayMode.Replace("(", string.Empty); actualOverlayMode = actualOverlayMode.Trim(); @@ -729,7 +753,7 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) var isHighGradeEdition = dataLoader.IsHighGradeEdition ? 1 : 0; var refreshRate = s.RefreshRate; - var questData = string.Format( + var questData = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}{28}{29}{30}{31}{32}{33}{34}{35}{36}{37}{38}{39}{40}{41}{42}{43}{44}{45}{46}{47}", runID, createdAt, createdBy, questID, timeLeft, finalTimeValue, finalTimeDisplay, objectiveImage, objectiveTypeID, objectiveQuantity, @@ -803,13 +827,12 @@ public int InsertQuestData(DataLoader dataLoader, int attempts) sql = "SELECT LAST_INSERT_ROWID()"; using (var cmd = new SQLiteCommand(sql, conn)) { - runID = Convert.ToInt32(cmd.ExecuteScalar()); + runID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } if (dataLoader.Model.PartySize() == 1) { long personalBest = 0; - var questID = dataLoader.Model.QuestID(); var weaponType = dataLoader.Model.WeaponType(); var improvedPersonalBest = false; @@ -854,7 +877,6 @@ FinalTimeValue ASC @Attempts)"; using (var cmd = new SQLiteCommand(sql, conn)) { - var finalTimeValue = model.TimeDefInt() - model.TimeInt(); if (finalTimeValue < personalBest || personalBest == 0) { improvedPersonalBest = true; @@ -925,7 +947,7 @@ FinalTimeValue ASC var mhfohddllHash = CalculateFileHash(gameFolderPath, @"\mhfo-hd.dll"); var mhfexeHash = CalculateFileHash(gameFolderPath, @"\mhf.exe"); - var gameFolderData = string.Format( + var gameFolderData = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}", createdAt, createdBy, runID, gameFolderPath, mhfdatHash, mhfemdHash, @@ -1005,7 +1027,7 @@ FinalTimeValue ASC int zenithSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - zenithSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + zenithSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO AutomaticSkills ( @@ -1055,7 +1077,7 @@ FinalTimeValue ASC int automaticSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - automaticSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + automaticSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO ActiveSkills ( @@ -1157,7 +1179,7 @@ FinalTimeValue ASC int activeSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - activeSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + activeSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO CaravanSkills ( @@ -1195,7 +1217,7 @@ FinalTimeValue ASC int caravanSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - caravanSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + caravanSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO StyleRankSkills ( @@ -1229,7 +1251,7 @@ FinalTimeValue ASC int styleRankSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - styleRankSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + styleRankSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO PlayerInventory ( @@ -1417,7 +1439,7 @@ FinalTimeValue ASC int playerInventoryID; using (var cmd = new SQLiteCommand(sql, conn)) { - playerInventoryID = Convert.ToInt32(cmd.ExecuteScalar()); + playerInventoryID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO AmmoPouch ( @@ -1525,7 +1547,7 @@ FinalTimeValue ASC int ammoPouchID; using (var cmd = new SQLiteCommand(sql, conn)) { - ammoPouchID = Convert.ToInt32(cmd.ExecuteScalar()); + ammoPouchID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO PartnyaBag ( @@ -1633,7 +1655,7 @@ FinalTimeValue ASC int partnyaBagID; using (var cmd = new SQLiteCommand(sql, conn)) { - partnyaBagID = Convert.ToInt32(cmd.ExecuteScalar()); + partnyaBagID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } sql = @"INSERT INTO RoadDureSkills ( @@ -1791,9 +1813,28 @@ FinalTimeValue ASC int roadDureSkillsID; using (var cmd = new SQLiteCommand(sql, conn)) { - roadDureSkillsID = Convert.ToInt32(cmd.ExecuteScalar()); + roadDureSkillsID = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } + sql = @"INSERT INTO QuestsToggleMode ( + QuestToggleMode, + RunID + ) VALUES ( + @QuestToggleMode, + @RunID + )"; + + using (var cmd = new SQLiteCommand(sql, conn)) + { + var questToggleMode = model.QuestToggleMonsterMode(); + + cmd.Parameters.AddWithValue("@QuestToggleMode", questToggleMode); + cmd.Parameters.AddWithValue("@RunID", runID); + cmd.ExecuteNonQuery(); + } + + Logger.Debug("Inserted into QuestsToggleMode table"); + var gearName = s.GearDescriptionExport; if (string.IsNullOrEmpty(gearName)) { @@ -1975,7 +2016,7 @@ FinalTimeValue ASC @PartnyaBagDictionary-- TEXT NOT NULL, )"; - var playerGearData = string.Format( + var playerGearData = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}{14}{15}{16}{17}{18}{19}{20}{21}{22}{23}{24}{25}{26}{27}{28}{29}{30}{31}{32}{33}{34}{35}{36}{37}{38}{39}{40}{41}{42}{43}{44}{45}{46}{47}{48}{49}{50}", createdAt, createdBy, runID, playerID, gearName, styleID, weaponIconID, weaponClassID, weaponTypeID, blademasterWeaponID, @@ -2078,7 +2119,7 @@ FinalTimeValue ASC // Handle a SQL exception Logger.Error(ex, "An error occurred while accessing the database"); MessageBox.Show( - string.Format( + string.Format(CultureInfo.InvariantCulture, @"An error occurred while accessing the database. Sql State: {0} @@ -2123,9 +2164,11 @@ FinalTimeValue ASC this.UpdateHashSets(conn); } - Logger.Debug("Inserted quest data, returning runID {0}", runID); + Logger.Debug(CultureInfo.InvariantCulture, "Inserted quest data, returning runID {0} and questID {1}", runID, questID); + WeakReferenceMessenger.Default.Send(new RunIDMessage(runID)); + WeakReferenceMessenger.Default.Send(new QuestIDMessage(questID)); dataLoader.Model.ShowSaveIcon = false; - return runID; + return (runID, questID); } public static void MakeDeserealizedQuestInfoDictionariesFromRunID(SQLiteConnection conn, DataLoader dataLoader, int runID) @@ -2192,10 +2235,12 @@ private void UpdateHashSets(SQLiteConnection conn) var lastPersonalBestAttempt = this.GetLastPersonalBestAttempt(conn); var lastPlayerGear = this.GetLastPlayerGear(conn); var lastActiveSkills = this.GetLastActiveSkills(conn); + var lastZenithSkills = this.GetLastZenithSkills(conn); var lastStyleRankSkills = this.GetLastStyleRankSkills(conn); var lastQuestAttempts = this.GetLastQuestAttempt(conn); var lastGachaCard = this.GetLastGachaCard(conn); var lastPlayerInventory = this.GetLastPlayerInventory(conn); + var lastQuestsToggleMode = this.GetLastQuestsToggleMode(conn); if (lastQuest.RunID != 0) { @@ -2278,6 +2323,15 @@ private void UpdateHashSets(SQLiteConnection conn) } } + if (lastZenithSkills.ZenithSkillsID != 0) + { + var zenithSkillsAdded = this.AllZenithSkills.Add(lastZenithSkills); + if (!zenithSkillsAdded) + { + Logger.Warn(CultureInfo.InvariantCulture, "Last zenith skills already found in hash set"); + } + } + if (lastStyleRankSkills.StyleRankSkillsID != 0) { var stylerankSkillsAdded = this.AllStyleRankSkills.Add(lastStyleRankSkills); @@ -2313,6 +2367,15 @@ private void UpdateHashSets(SQLiteConnection conn) Logger.Warn(CultureInfo.InvariantCulture, "Last player inventory already found in hash set"); } } + + if (lastQuestsToggleMode.QuestsToggleModeID != 0) + { + var questsToggleModeAdded = this.AllQuestsToggleMode.Add(lastQuestsToggleMode); + if (!questsToggleModeAdded) + { + Logger.Warn(CultureInfo.InvariantCulture, "Last quests toggle mode already found in hash set"); + } + } } private void CreateDatabaseTriggers(SQLiteConnection conn) @@ -3030,7 +3093,7 @@ public void StoreSessionTime(DateTime programStart) // Handle a SQL exception Logger.Error(ex, "An error occurred while accessing the database"); MessageBox.Show( - string.Format( + string.Format(CultureInfo.InvariantCulture, @"An error occurred while accessing the database. SQL State: {0} @@ -3177,6 +3240,59 @@ public List GetPlayerAchievementIDList() return achievementIDList; } + /// + /// Unlocks the challenge and stores the unlock date of the challenge. + /// + /// + /// false if the challenge unlock date could not be stored + public bool UnlockChallenge(Challenge challenge, int challengeID) + { + if (challenge.UnlockDate != DateTime.UnixEpoch) + { + Logger.Error(CultureInfo.InvariantCulture, "Challenge {0} is already unlocked ({1})", challenge.Name, challenge.UnlockDate); + return false; + } + + if (string.IsNullOrEmpty(this.dataSource)) + { + Logger.Warn(CultureInfo.InvariantCulture, "Cannot unlock challenge. dataSource: {0}", this.dataSource); + return false; + } + + using (var conn = new SQLiteConnection(this.dataSource)) + { + conn.Open(); + using (var transaction = conn.BeginTransaction()) + { + try + { + var sql = @"INSERT INTO PlayerChallenges ( + UnlockDate, + ChallengeID + ) VALUES ( + @UnlockDate, + @ChallengeID)"; + + using (var cmd = new SQLiteCommand(sql, conn)) + { + cmd.Parameters.AddWithValue("@UnlockDate", DateTime.UtcNow); + cmd.Parameters.AddWithValue("@ChallengeID", challengeID); + + cmd.ExecuteNonQuery(); + } + + transaction.Commit(); + return true; + } + catch (Exception ex) + { + HandleError(transaction, ex); + return false; + } + } + } + } + private static bool IsDatabaseLocked(SQLiteConnection connection) { // Query the database to check if it's locked @@ -3429,7 +3545,7 @@ private void InsertPlayerDictionaryDataIntoTable(SQLiteConnection conn, DataLoad var result = cmd.ExecuteScalar(); if (result != null && result.ToString() != string.Empty) { - startTime = Convert.ToDateTime(result); + startTime = Convert.ToDateTime(result, CultureInfo.InvariantCulture); } else { @@ -3481,11 +3597,11 @@ private void InsertPlayerDictionaryDataIntoTable(SQLiteConnection conn, DataLoad if (playerID == 1 && (startTime == DateTime.UnixEpoch || startTime == DateTime.MinValue)) { - creationDate = DateTime.UtcNow.Date.ToString(CultureInfo.InvariantCulture); + creationDate = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"); } else { - creationDate = startTime.Date.ToString(CultureInfo.InvariantCulture); + creationDate = startTime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"); } if (playerID == 1) @@ -3781,7 +3897,7 @@ public void StoreAchievements(List achievementsID) public void StoreAchievement(int achievementID) { - Logger.Debug("Storing achievement ID {0}", achievementID); + Logger.Debug(CultureInfo.InvariantCulture, "Storing achievement ID {0}", achievementID); if (string.IsNullOrEmpty(this.dataSource)) { @@ -3832,15 +3948,6 @@ public void StoreAchievement(int achievementID) } } - // TODO put somewhere else and test - public static string FormatTime(int framesElapsed) - { - var minutes = framesElapsed / (Numbers.FramesPerSecond * 60); - var seconds = (framesElapsed % (Numbers.FramesPerSecond * 60)) / Numbers.FramesPerSecond; - var milliseconds = ((framesElapsed % (Numbers.FramesPerSecond * 60)) % Numbers.FramesPerSecond) / double.Parse(Numbers.FramesPerSecond.ToString(), CultureInfo.InvariantCulture); - return $"{minutes:D2}:{seconds:D2}.{(int)(milliseconds * 1000):D3}"; - } - private void CreateDatabaseTables(SQLiteConnection conn, DataLoader dataLoader) { using (var transaction = conn.BeginTransaction()) @@ -4832,6 +4939,15 @@ Score INTEGER NOT NULL DEFAULT 0 cmd.ExecuteNonQuery(); } + sql = @"CREATE TABLE IF NOT EXISTS PlayerBingoPoints( + PlayerBingoPointsID INTEGER PRIMARY KEY, + Points INTEGER NOT NULL DEFAULT 0 + )"; + using (var cmd = new SQLiteCommand(sql, conn)) + { + cmd.ExecuteNonQuery(); + } + sql = @"CREATE TABLE IF NOT EXISTS MezFesMinigames( MezFesMinigameID INTEGER PRIMARY KEY AUTOINCREMENT, MezFesMinigameName TEXT NOT NULL @@ -5027,6 +5143,29 @@ FOREIGN KEY(RunID) REFERENCES Quests(RunID) cmd.ExecuteNonQuery(); } + // Instead of using foreign keys, just combine the Dictionary with the UnlockDate here. + sql = @"CREATE TABLE IF NOT EXISTS PlayerChallenges( + PlayerChallengesID INTEGER PRIMARY KEY AUTOINCREMENT, + UnlockDate TEXT NOT NULL, + ChallengeID INTEGER NOT NULL, + UNIQUE(ChallengeID) ON CONFLICT IGNORE + )"; + using (var cmd = new SQLiteCommand(sql, conn)) + { + cmd.ExecuteNonQuery(); + } + + sql = @"CREATE TABLE IF NOT EXISTS QuestsToggleMode( + QuestsToggleModeID INTEGER PRIMARY KEY AUTOINCREMENT, + QuestToggleMode INTEGER NOT NULL DEFAULT 0, + RunID INTEGER NOT NULL, + FOREIGN KEY(RunID) REFERENCES Quests(RunID) + )"; + using (var cmd = new SQLiteCommand(sql, conn)) + { + cmd.ExecuteNonQuery(); + } + // a mh game but like a MUD. hunt in-game to get many kinds of points for this game. hunt and tame monsters. challenge other CPU players/monsters. sql = @"CREATE TABLE IF NOT EXISTS GachaMaterial( GachaMaterialID INTEGER PRIMARY KEY, @@ -5691,7 +5830,7 @@ FinalTimeValue ASC time = reader.GetInt64(reader.GetOrdinal("TimeLeft")); } - personalBest = ViewModels.Windows.AddressModel.GetMinutesSecondsMillisecondsFromFrames(time); + personalBest = TimeService.GetMinutesSecondsMillisecondsFromFrames(time); } else { @@ -5725,10 +5864,14 @@ public async Task GetPersonalBestAsync(long questID, int weaponTypeID, s await conn.OpenAsync(); using (var transaction = conn.BeginTransaction()) { - try + var numRetries = 0; + var success = false; + while (!success && numRetries < 3) { - using (var cmd = new SQLiteCommand( - @"SELECT + try + { + using (var cmd = new SQLiteCommand( + @"SELECT TimeLeft, FinalTimeValue, FinalTimeDisplay, @@ -5746,37 +5889,51 @@ Quests q ORDER BY FinalTimeValue ASC LIMIT 1", conn)) - { - cmd.Parameters.AddWithValue("@questID", questID); - cmd.Parameters.AddWithValue("@weaponTypeID", weaponTypeID); - cmd.Parameters.AddWithValue("@category", category); - - var reader = await cmd.ExecuteReaderAsync(); - if (await reader.ReadAsync()) { - long time = 0; - if (timerMode == "Elapsed") + cmd.Parameters.AddWithValue("@questID", questID); + cmd.Parameters.AddWithValue("@weaponTypeID", weaponTypeID); + cmd.Parameters.AddWithValue("@category", category); + + var reader = await cmd.ExecuteReaderAsync(); + if (await reader.ReadAsync()) { - time = reader.GetInt64(reader.GetOrdinal("FinalTimeValue")); + long time = 0; + if (timerMode == "Elapsed") + { + time = reader.GetInt64(reader.GetOrdinal("FinalTimeValue")); + } + else + { + time = reader.GetInt64(reader.GetOrdinal("TimeLeft")); + } + + personalBest = TimeService.GetMinutesSecondsMillisecondsFromFrames(time); } else { - time = reader.GetInt64(reader.GetOrdinal("TimeLeft")); + personalBest = Messages.TimerNotLoaded; } + } - personalBest = ViewModels.Windows.AddressModel.GetMinutesSecondsMillisecondsFromFrames(time); + transaction.Commit(); + success = true; + } + catch (SQLiteException ex) + { + if (ex.ResultCode is SQLiteErrorCode.Locked or SQLiteErrorCode.Busy) + { + // Database is locked, retry after a short delay + numRetries++; + Logger.Warn(CultureInfo.InvariantCulture, ex); + Thread.Sleep(1_000); } else { - personalBest = Messages.TimerNotLoaded; + // Some other error occurred, abort the transaction + HandleError(transaction, ex); + break; } } - - transaction.Commit(); - } - catch (Exception ex) - { - HandleError(transaction, ex); } } } @@ -6011,7 +6168,7 @@ DO UPDATE command.Parameters.AddWithValue("@WeaponTypeID", weaponTypeID); command.Parameters.AddWithValue("@ActualOverlayMode", category); - attempts = Convert.ToInt32(command.ExecuteScalar()); + attempts = Convert.ToInt32(command.ExecuteScalar(), CultureInfo.InvariantCulture); } transaction.Commit(); @@ -6023,6 +6180,7 @@ DO UPDATE { // Database is locked, retry after a short delay numRetries++; + Logger.Warn(CultureInfo.InvariantCulture, ex); Thread.Sleep(1_000); } else @@ -6078,7 +6236,7 @@ DO UPDATE command.Parameters.AddWithValue("@WeaponTypeID", weaponTypeID); command.Parameters.AddWithValue("@ActualOverlayMode", category); - attempts = Convert.ToInt32(command.ExecuteScalar()); + attempts = Convert.ToInt32(command.ExecuteScalar(), CultureInfo.InvariantCulture); } transaction.Commit(); @@ -6090,6 +6248,7 @@ DO UPDATE { // Database is locked, retry after a short delay numRetries++; + Logger.Warn(CultureInfo.InvariantCulture, ex); Thread.Sleep(1_000); } else @@ -6145,7 +6304,7 @@ DO UPDATE command.Parameters.AddWithValue("@WeaponTypeID", weaponTypeID); command.Parameters.AddWithValue("@ActualOverlayMode", category); - attempts = Convert.ToInt32(await command.ExecuteScalarAsync()); + attempts = Convert.ToInt32(await command.ExecuteScalarAsync(), CultureInfo.InvariantCulture); } transaction.Commit(); @@ -6157,6 +6316,7 @@ DO UPDATE { // Database is locked, retry after a short delay numRetries++; + Logger.Warn(CultureInfo.InvariantCulture, ex); await Task.Delay(1_000); } else @@ -6212,7 +6372,7 @@ DO UPDATE command.Parameters.AddWithValue("@WeaponTypeID", weaponTypeID); command.Parameters.AddWithValue("@ActualOverlayMode", category); - attempts = Convert.ToInt32(await command.ExecuteScalarAsync()); + attempts = Convert.ToInt32(await command.ExecuteScalarAsync(), CultureInfo.InvariantCulture); } transaction.Commit(); @@ -6224,6 +6384,7 @@ DO UPDATE { // Database is locked, retry after a short delay numRetries++; + Logger.Warn(CultureInfo.InvariantCulture, ex); await Task.Delay(1_000); } else @@ -6275,7 +6436,7 @@ public long GetQuestAttempts(long questID, int weaponTypeID, string category) var result = cmd.ExecuteScalar(); if (result != null) { - attempts = Convert.ToInt64(result); + attempts = Convert.ToInt64(result, CultureInfo.InvariantCulture); } } @@ -6653,7 +6814,7 @@ private Bingo GetLastBingo(SQLiteConnection conn) Score = long.Parse(reader["Score"]?.ToString() ?? "0", CultureInfo.InvariantCulture), MonsterList = JsonConvert.DeserializeObject>(reader["MonsterList"]?.ToString() ?? "{}") ?? new List { }, WeaponType = reader["WeaponType"]?.ToString() ?? string.Empty, - Category = reader["Category"]?.ToString() ?? string.Empty, + Category = ChallengeService.ConvertToBingoGauntletCategory(reader.GetInt64(reader.GetOrdinal("Category"))), TotalFramesElapsed = long.Parse(reader["TotalFramesElapsed"]?.ToString() ?? "0", CultureInfo.InvariantCulture), TotalTimeElapsed = reader["TotalTimeElapsed"]?.ToString() ?? string.Empty, }; @@ -6672,6 +6833,96 @@ private Bingo GetLastBingo(SQLiteConnection conn) return last; } + public long GetPlayerBingoPoints() + { + long points = 0; + + if (string.IsNullOrEmpty(this.dataSource)) + { + Logger.Warn(CultureInfo.InvariantCulture, "Cannot get player bingo points. dataSource: {0}", this.dataSource); + return points; + } + + using (var conn = new SQLiteConnection(this.dataSource)) + { + conn.Open(); + + using (var transaction = conn.BeginTransaction()) + { + try + { + using (var cmd = new SQLiteCommand("SELECT Points FROM PlayerBingoPoints WHERE PlayerBingoPointsID = 1", conn)) + { + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + points = long.Parse(reader["Points"]?.ToString() ?? "0", CultureInfo.InvariantCulture); + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + } + + return points; + } + + public void SetPlayerBingoPoints(long points) + { + if (string.IsNullOrEmpty(this.dataSource)) + { + Logger.Warn(CultureInfo.InvariantCulture, "Cannot set player bingo points. dataSource: {0}", this.dataSource); + return; + } + + using (var conn = new SQLiteConnection(this.dataSource)) + { + conn.Open(); + using (var transaction = conn.BeginTransaction()) + { + try + { + // Create a command that will be used to insert multiple rows in a batch + using (var cmd = new SQLiteCommand(conn)) + { + // Set the command text to insert a single row + cmd.CommandText = @"INSERT OR REPLACE INTO PlayerBingoPoints ( + PlayerBingoPointsID, + Points + ) VALUES ( + @PlayerBingoPointsID, + @Points)"; + + // Add the parameter placeholders + cmd.Parameters.Add("@PlayerBingoPointsID", DbType.Int64); + cmd.Parameters.Add("@Points", DbType.Int64); + + // Set the parameter values + cmd.Parameters["@PlayerBingoPointsID"].Value = 1; + cmd.Parameters["@Points"].Value = points; + + cmd.ExecuteNonQuery(); + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + } + + Logger.Debug("Inserted into PlayerBingoPoints table value {0}", points); + } + private ZenithGauntlet GetLastZenithGauntlet(SQLiteConnection conn) { ZenithGauntlet last = new (); @@ -7029,6 +7280,48 @@ private ActiveSkills GetLastActiveSkills(SQLiteConnection conn) return last; } + private ZenithSkills GetLastZenithSkills(SQLiteConnection conn) + { + ZenithSkills last = new(); + using (var transaction = conn.BeginTransaction()) + { + try + { + using (var cmd = new SQLiteCommand("SELECT * FROM ZenithSkills ORDER BY ZenithSkillsID DESC LIMIT 1", conn)) + { + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + last = new ZenithSkills + { + CreatedAt = DateTime.Parse(reader["CreatedAt"]?.ToString() ?? DateTime.UnixEpoch.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture), + CreatedBy = reader["CreatedBy"]?.ToString() ?? string.Empty, + ZenithSkillsID = long.Parse(reader["ZenithSkillsID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + RunID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill1ID = long.Parse(reader["ZenithSkill1ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill2ID = long.Parse(reader["ZenithSkill2ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill3ID = long.Parse(reader["ZenithSkill3ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill4ID = long.Parse(reader["ZenithSkill4ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill5ID = long.Parse(reader["ZenithSkill5ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill6ID = long.Parse(reader["ZenithSkill6ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill7ID = long.Parse(reader["ZenithSkill7ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + }; + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + + return last; + } + private StyleRankSkills GetLastStyleRankSkills(SQLiteConnection conn) { StyleRankSkills last = new (); @@ -7174,6 +7467,40 @@ private PlayerInventory GetLastPlayerInventory(SQLiteConnection conn) return last; } + private QuestsToggleMode GetLastQuestsToggleMode(SQLiteConnection conn) + { + QuestsToggleMode last = new(); + using (var transaction = conn.BeginTransaction()) + { + try + { + using (var cmd = new SQLiteCommand("SELECT * FROM QuestsToggleMode ORDER BY QuestsToggleModeID DESC LIMIT 1", conn)) + { + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + last = new QuestsToggleMode + { + QuestsToggleModeID = long.Parse(reader["QuestsToggleModeID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + QuestToggleMode = long.Parse(reader["QuestToggleMode"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + RunID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + }; + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + + return last; + } + /// /// Loads the database data into hash sets. This is used to avoid querying the database when checking for achievement unlocks. /// When inserting into these hashsets, the database must also be updated, and viceversa. @@ -7203,10 +7530,12 @@ public void LoadDatabaseDataIntoHashSets(Grid saveIconGrid, DataLoader dataLoade this.AllPersonalBestAttempts = this.GetAllPersonalBestAttempts(conn); this.AllPlayerGear = this.GetAllPlayerGear(conn); this.AllActiveSkills = this.GetAllActiveSkills(conn); + this.AllZenithSkills = this.GetAllZenithSkills(conn); this.AllStyleRankSkills = this.GetAllStyleRankSkills(conn); this.AllQuestAttempts = this.GetAllQuestAttempts(conn); this.AllGachaCards = this.GetAllGachaCards(conn); this.AllPlayerInventories = this.GetAllPlayerInventories(conn); + this.AllQuestsToggleMode = this.GetAllQuestsToggleMode(conn); } } catch (Exception ex) @@ -7345,6 +7674,42 @@ private HashSet GetAllPlayerInventories(SQLiteConnection conn) return hashSet; } + private HashSet GetAllQuestsToggleMode(SQLiteConnection conn) + { + HashSet hashSet = new(); + using (var transaction = conn.BeginTransaction()) + { + try + { + using (var cmd = new SQLiteCommand(@"SELECT * FROM QuestsToggleMode", conn)) + { + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + QuestsToggleMode data = new() + { + QuestsToggleModeID = long.Parse(reader["QuestsToggleModeID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + QuestToggleMode = long.Parse(reader["QuestToggleMode"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + RunID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + }; + + hashSet.Add(data); + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + + return hashSet; + } + private HashSet GetAllGachaCards(SQLiteConnection conn) { HashSet hashSet = new (); @@ -7566,6 +7931,50 @@ private HashSet GetAllActiveSkills(SQLiteConnection conn) return hashSet; } + private HashSet GetAllZenithSkills(SQLiteConnection conn) + { + HashSet hashSet = new(); + using (var transaction = conn.BeginTransaction()) + { + try + { + using (var cmd = new SQLiteCommand(@"SELECT * FROM ZenithSkills", conn)) + { + using (var reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + ZenithSkills data = new() + { + ZenithSkillsID = long.Parse(reader["ZenithSkillsID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + CreatedAt = DateTime.Parse(reader["CreatedAt"]?.ToString() ?? DateTime.UnixEpoch.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture), + CreatedBy = reader["CreatedBy"]?.ToString() ?? string.Empty, + RunID = long.Parse(reader["RunID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill1ID = long.Parse(reader["ZenithSkill1ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill2ID = long.Parse(reader["ZenithSkill2ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill3ID = long.Parse(reader["ZenithSkill3ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill4ID = long.Parse(reader["ZenithSkill4ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill5ID = long.Parse(reader["ZenithSkill5ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill6ID = long.Parse(reader["ZenithSkill6ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + ZenithSkill7ID = long.Parse(reader["ZenithSkill7ID"]?.ToString() ?? "0", CultureInfo.InvariantCulture), + }; + + hashSet.Add(data); + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + + return hashSet; + } + private HashSet GetAllStyleRankSkills(SQLiteConnection conn) { HashSet hashSet = new (); @@ -7817,7 +8226,7 @@ private HashSet GetAllBingo(SQLiteConnection conn) Difficulty = (Difficulty)long.Parse(reader["Difficulty"]?.ToString() ?? "0", CultureInfo.InvariantCulture), MonsterList = JsonConvert.DeserializeObject>(reader["MonsterList"]?.ToString() ?? "{}") ?? new List { }, WeaponType = reader["WeaponType"]?.ToString() ?? "0", - Category = reader["Category"]?.ToString() ?? "0", + Category = ChallengeService.ConvertToBingoGauntletCategory(reader.GetInt64(reader.GetOrdinal("Category"))), TotalFramesElapsed = long.Parse(reader["TotalFramesElapsed"]?.ToString() ?? "0", CultureInfo.InvariantCulture), TotalTimeElapsed = reader["TotalTimeElapsed"]?.ToString() ?? "0", Score = long.Parse(reader["Score"]?.ToString() ?? "0", CultureInfo.InvariantCulture), @@ -9903,6 +10312,63 @@ ORDER BY return achievements; } + /// + /// Get a list of all challenges where the unlock date is set or not by the player. + /// + /// + public ReadOnlyDictionary GetPlayerChallenges() + { + var data = Challenges.IDChallenge; + if (string.IsNullOrEmpty(this.dataSource)) + { + Logger.Warn(CultureInfo.InvariantCulture, "Cannot get player challenges collection. dataSource: {0}", this.dataSource); + return data; + } + + using (var conn = new SQLiteConnection(this.dataSource)) + { + conn.Open(); + using (var transaction = conn.BeginTransaction()) + { + try + { + var sql = @"SELECT * FROM PlayerChallenges ORDER BY ChallengeID ASC"; + + using (var cmd = new SQLiteCommand(sql, conn)) + { + using (var reader = cmd.ExecuteReader()) + { + if (reader == null || !reader.HasRows) + { + return data; + } + + if (reader.HasRows) + { + while (reader.Read()) + { + var challengeID = reader.GetInt32(reader.GetOrdinal("ChallengeID")); + data[challengeID].UnlockDate = reader.IsDBNull(reader.GetOrdinal("UnlockDate")) +? DateTime.UnixEpoch +: DateTime.Parse(reader.GetString(reader.GetOrdinal("UnlockDate")), CultureInfo.InvariantCulture); + break; + } + } + } + } + + transaction.Commit(); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + } + + return data; + } + public ObservableCollection GetRecentRuns() { var recentRuns = new ObservableCollection(); @@ -10140,8 +10606,8 @@ GROUP BY // LiveChart graph // use a switch statement or a lookup table to convert the // weaponTypeID and styleID to their corresponding string names - weaponType = WeaponType.IDName[int.Parse(weaponTypeID.ToString(), CultureInfo.InvariantCulture)]; - style = WeaponStyle.IDName[int.Parse(styleID.ToString(), CultureInfo.InvariantCulture)]; + weaponType = WeaponType.IDName[int.Parse(weaponTypeID.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)]; + style = WeaponStyle.IDName[int.Parse(styleID.ToString(CultureInfo.InvariantCulture), CultureInfo.InvariantCulture)]; weaponUsageData.Add(new WeaponUsage(weaponType, style, (int)runCount)); } } @@ -10229,7 +10695,7 @@ INNER JOIN reader.Read(); configWindow.SelectedQuestObjectiveImage.Source = new BitmapImage(new Uri(reader["ObjectiveImage"]?.ToString() ?? "0")); var questName = reader["QuestNameName"].ToString(); - configWindow.SelectedQuestNameTextBlock.Text = questName == string.Empty ? string.Format("{0} {1}", Messages.CustomQuestName, questID) : questName; + configWindow.SelectedQuestNameTextBlock.Text = questName == string.Empty ? string.Format(CultureInfo.InvariantCulture, "{0} {1}", Messages.CustomQuestName, questID) : questName; configWindow.SelectedQuestObjectiveTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", ObjectiveType.IDName[int.Parse(reader["ObjectiveTypeID"]?.ToString() ?? "0", CultureInfo.InvariantCulture)], reader["ObjectiveQuantity"], reader["ObjectiveName"]); configWindow.CurrentTimeTextBlock.Text = DateTime.UtcNow.ToString(CultureInfo.InvariantCulture); } @@ -10276,7 +10742,7 @@ GROUP BY while (reader.Read()) { - WeaponType.IDName.TryGetValue(Convert.ToInt32(reader["WeaponTypeID"]), out var weaponType); + WeaponType.IDName.TryGetValue(Convert.ToInt32(reader["WeaponTypeID"], CultureInfo.InvariantCulture), out var weaponType); int bestTime; if (reader["BestTime"] == DBNull.Value) @@ -10301,59 +10767,59 @@ GROUP BY switch (weaponType) { case "Sword and Shield": - configWindow.SwordAndShieldBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.SwordAndShieldBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.SwordAndShieldRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Great Sword": - configWindow.GreatSwordBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.GreatSwordBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.GreatSwordRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Dual Swords": - configWindow.DualSwordsBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.DualSwordsBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.DualSwordsRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Long Sword": - configWindow.LongSwordBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.LongSwordBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.LongSwordRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Lance": - configWindow.LanceBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.LanceBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.LanceRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Gunlance": - configWindow.GunlanceBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.GunlanceBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.GunlanceRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Hammer": - configWindow.HammerBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.HammerBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.HammerRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Hunting Horn": - configWindow.HuntingHornBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.HuntingHornBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.HuntingHornRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Tonfa": - configWindow.TonfaBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.TonfaBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.TonfaRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Switch Axe F": - configWindow.SwitchAxeFBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.SwitchAxeFBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.SwitchAxeFRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Magnet Spike": - configWindow.MagnetSpikeBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.MagnetSpikeBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.MagnetSpikeRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Light Bowgun": - configWindow.LightBowgunBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.LightBowgunBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.LightBowgunRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Heavy Bowgun": - configWindow.HeavyBowgunBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.HeavyBowgunBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.HeavyBowgunRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; case "Bow": - configWindow.BowBestTimeTextBlock.Text = FormatTime(bestTime); + configWindow.BowBestTimeTextBlock.Text = TimeService.GetMinutesSecondsMillisecondsFromFrames(bestTime); configWindow.BowRunIDTextBlock.Text = string.Format(CultureInfo.InvariantCulture, "Run ID: {0}", runID); break; default: @@ -11587,7 +12053,7 @@ public long GetTotalQuestTimeElapsed() { if (reader.Read()) { - totalTimeElapsed = Convert.ToInt64(reader["TotalTimeElapsed"]); + totalTimeElapsed = Convert.ToInt64(reader["TotalTimeElapsed"], CultureInfo.InvariantCulture); } } } @@ -11640,7 +12106,7 @@ FROM Quests { if (reader.Read()) { - var value = Convert.ToInt64(reader["TotalTimeElapsed"]); + var value = Convert.ToInt64(reader["TotalTimeElapsed"], CultureInfo.InvariantCulture); questCompendium.TotalTimeElapsedQuests = value; } @@ -11705,7 +12171,7 @@ FROM Quests { while (reader.Read()) { - var questId = Convert.ToInt32(reader["QuestId"]); + var questId = Convert.ToInt32(reader["QuestId"], CultureInfo.InvariantCulture); var cartsDictionaryJson = reader.GetString(1); // Deserialize carts dictionary JSON string @@ -11877,7 +12343,7 @@ private static long GetCountOfIntValue(string fieldName, string tableName, int v using (var cmd = new SQLiteCommand(query, conn)) { cmd.Parameters.AddWithValue("@value", value); - count = Convert.ToInt64(cmd.ExecuteScalar()); + count = Convert.ToInt64(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } return count; @@ -12151,7 +12617,7 @@ private static double GetAverageHitsTakenBlockedCount(SQLiteConnection conn) using (var cmd = new SQLiteCommand(totalQuestRunsQuery, conn)) { - totalQuestRuns = Convert.ToInt32(cmd.ExecuteScalar()); + totalQuestRuns = Convert.ToInt32(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } using (var cmd = new SQLiteCommand(hitsTakenBlockedCountQuery, conn)) @@ -12319,7 +12785,7 @@ private static long GetTableRowCount(string fieldName, string tableName, SQLiteC var sql = $"SELECT COUNT({fieldName}) FROM {tableName}"; using (var command = new SQLiteCommand(sql, conn)) { - return Convert.ToInt64(command.ExecuteScalar()); + return Convert.ToInt64(command.ExecuteScalar(), CultureInfo.InvariantCulture); } } @@ -12338,7 +12804,7 @@ private static long GetCountOfStringValue(string fieldName, string tableName, st using (var cmd = new SQLiteCommand(query, conn)) { cmd.Parameters.AddWithValue("@value", value); - count = Convert.ToInt64(cmd.ExecuteScalar()); + count = Convert.ToInt64(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); } return count; @@ -12764,7 +13230,7 @@ private static long GetSumValue(string field, string table, SQLiteConnection con var query = $"SELECT TOTAL({field}) FROM {table} WHERE {field} IS NOT NULL"; using (var cmd = new SQLiteCommand(query, conn)) { - var sum = Convert.ToInt64(cmd.ExecuteScalar()); + var sum = Convert.ToInt64(cmd.ExecuteScalar(), CultureInfo.InvariantCulture); return sum; } } @@ -12918,7 +13384,7 @@ FROM QuestAttempts { if (reader.Read()) { - attempts = Convert.ToInt64(reader["TotalAttempts"]); + attempts = Convert.ToInt64(reader["TotalAttempts"], CultureInfo.InvariantCulture); } } } @@ -12954,7 +13420,7 @@ LIMIT 1 if (reader.Read()) { questID = (long)reader["QuestID"]; - attempts = Convert.ToInt64(reader["Attempts"]); + attempts = Convert.ToInt64(reader["Attempts"], CultureInfo.InvariantCulture); } } } @@ -13356,7 +13822,7 @@ private int GetUserVersion(SQLiteConnection connection) var result = cmd.ExecuteScalar(); if (result != DBNull.Value) { - version = Convert.ToInt32(result); + version = Convert.ToInt32(result, CultureInfo.InvariantCulture); Logger.Info(CultureInfo.InvariantCulture, "Current user_version is {0}", version); } } @@ -13488,7 +13954,7 @@ private void MigrateToSchemaFromVersion(SQLiteConnection conn, int fromVersion) default: Logger.Info(CultureInfo.InvariantCulture, "No new schema updates found. Schema version: {0}", fromVersion); MessageBox.Show( - string.Format( + string.Format(CultureInfo.InvariantCulture, @"No new schema updates found! Schema version: {0}", fromVersion), string.Format(CultureInfo.InvariantCulture, "MHF-Z Overlay Database Update ({0} to {1})", previousVersion, @@ -13619,6 +14085,146 @@ private void CheckDatabaseVersion(SQLiteConnection connection, DataLoader dataLo } } + private void RemoveCurrentDatabaseTriggers(SQLiteConnection conn) + { + using (var transaction = conn.BeginTransaction()) + { + try + { + List triggersToRemove = new(); + + // SQL statement to retrieve all triggers in the database + string getTriggersSql = "SELECT name FROM sqlite_master WHERE type = 'trigger';"; + + // SQL statement to drop a trigger + string dropTriggerSql = "DROP TRIGGER IF EXISTS {0};"; + using (SQLiteCommand getTriggersCommand = new SQLiteCommand(getTriggersSql, conn)) + using (SQLiteDataReader reader = getTriggersCommand.ExecuteReader()) + { + while (reader.Read()) + { + string triggerName = reader.GetString(0); + + using (SQLiteCommand dropTriggerCommand = new SQLiteCommand(string.Format(dropTriggerSql, triggerName), conn)) + { + triggersToRemove.Add(triggerName); + dropTriggerCommand.ExecuteNonQuery(); + } + } + } + + transaction.Commit(); + Logger.Debug("Triggers to remove: {0}", triggersToRemove.Count); + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + } + + private void CheckDatesFormat(SQLiteConnection conn) + { + using (var transaction = conn.BeginTransaction()) + { + try + { + SQLiteCommand command = new SQLiteCommand("SELECT name FROM sqlite_master WHERE type='table'", conn); + SQLiteDataReader reader = command.ExecuteReader(); + + List tables = new List(); + while (reader.Read()) + { + var name = reader["name"].ToString(); + if (name is not null) + { + tables.Add(name); + } + } + + if (tables.Count <= 5) + { + Logger.Warn("Only 5 or less tables were found for date conversion, canceling process."); + return; + } + + List updatedFields = new List(); + + foreach (var table in tables) + { + SQLiteCommand command2 = new SQLiteCommand($"PRAGMA table_info({table})", conn); + SQLiteDataReader reader2 = command2.ExecuteReader(); + while (reader2.Read()) + { + var name2 = reader2["name"].ToString(); + if (name2 is null) + { + Logger.Warn("Column name is null."); + break; + } + + string columnName = name2; + if (columnName == "CreatedAt" || columnName == "CreationDate" || columnName == "Date" || columnName == "StartTime" || columnName == "EndTime") + { + SQLiteCommand selectCommand = new SQLiteCommand($"SELECT {columnName} FROM {table}", conn); + SQLiteDataReader selectReader = selectCommand.ExecuteReader(); + while (selectReader.Read()) + { + var selectedColumn = selectReader.GetString(selectReader.GetOrdinal(columnName)); + if (selectedColumn is null) + { + Logger.Warn("Selected column is null."); + break; + } + + string value = selectedColumn; + if (!value.EndsWith("Z")) + { + DateTime date; + if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out date)) + { + string utcDate = date.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"); + + SQLiteCommand updateCommand = new SQLiteCommand($"UPDATE {table} SET {columnName} = '{utcDate}' WHERE {columnName} = '{value}'", conn); + updateCommand.ExecuteNonQuery(); + + updatedFields.Add($"{table}.{columnName}.{value}.{utcDate}"); + } + else + { + string utcDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToString("yyyy-MM-dd HH:mm:ss.fffffffZ"); + + SQLiteCommand updateCommand = new SQLiteCommand($"UPDATE {table} SET {columnName} = '{utcDate}' WHERE {columnName} = '{value}'", conn); + updateCommand.ExecuteNonQuery(); + + updatedFields.Add($"{table}.{columnName}.{value}.{utcDate}"); + } + } + } + } + } + } + + transaction.Commit(); + + if (updatedFields.Count > 0) + { + MessageBox.Show($"Found dates needed to convert to UTC. Updated date entries count: {updatedFields.Count}", Messages.InfoTitle, MessageBoxButton.OK, MessageBoxImage.Information); + Logger.Debug($"Updated date entries: {string.Join("\n", updatedFields)}"); + Logger.Debug($"Updated date entries count: {updatedFields.Count}"); + } + else + { + Logger.Info("No date conversions needed in database."); + } + } + catch (Exception ex) + { + HandleError(transaction, ex); + } + } + } + private void UpdateDatabaseSchema(SQLiteConnection connection, DataLoader dataLoader, int currentUserVersion) { dataLoader.Model.ShowSaveIcon = true; @@ -13918,7 +14524,7 @@ FOREIGN KEY(ObjectiveTypeID) REFERENCES ObjectiveType(ObjectiveTypeID) createTable.ExecuteNonQuery(); } - Logger.Debug("Created table if not exists new_{0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Created table if not exists new_{0}", tableName); // 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. // Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X @@ -13927,9 +14533,9 @@ FOREIGN KEY(ObjectiveTypeID) REFERENCES ObjectiveType(ObjectiveTypeID) var countQuery = "SELECT COUNT(*) FROM Quests"; using (var command = new SQLiteCommand(countQuery, connection)) { - var rowCount = Convert.ToInt32(command.ExecuteScalar()); + var rowCount = Convert.ToInt32(command.ExecuteScalar(), CultureInfo.InvariantCulture); - Logger.Debug("Inserting default values into new_Quests"); + Logger.Debug(CultureInfo.InvariantCulture, "Inserting default values into new_Quests"); // Insert rows with default values into new_Quests var insertQuery = $"INSERT INTO new_Quests DEFAULT VALUES"; @@ -13979,7 +14585,7 @@ v0.24 to v0.25 (fixing the refreshrate check is above, but the fix is implemente dropTable.ExecuteNonQuery(); } - Logger.Debug("Deleted table {0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Deleted table {0}", tableName); // 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. // Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X @@ -13988,7 +14594,7 @@ v0.24 to v0.25 (fixing the refreshrate check is above, but the fix is implemente renameTable.ExecuteNonQuery(); } - Logger.Debug("Renamed new_{0} to {1}", tableName, tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Renamed new_{0} to {1}", tableName, tableName); // 8. Use CREATE INDEX, CREATE TRIGGER, and CREATE VIEW to reconstruct indexes, triggers, and views associated with table X. Perhaps use the old format of the triggers, indexes, and views saved from step 3 above as a guide, making changes as appropriate for the alteration. // Use CREATE INDEX, CREATE TRIGGER, and CREATE VIEW to reconstruct indexes, triggers, and views associated with table X @@ -14055,7 +14661,7 @@ v0.24 to v0.25 (fixing the refreshrate check is above, but the fix is implemente } } - Logger.Debug("Views affected: {0}", viewSqlsModified.Count); + Logger.Debug(CultureInfo.InvariantCulture, "Views affected: {0}", viewSqlsModified.Count); Logger.Info(CultureInfo.InvariantCulture, "Altered table {0} successfully", tableName); } @@ -14081,7 +14687,7 @@ private static void AlterTablePersonalBestAttempts(SQLiteConnection connection, createTable.ExecuteNonQuery(); } - Logger.Debug("Created table if not exists new_{0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Created table if not exists new_{0}", tableName); // 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. // Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X @@ -14094,7 +14700,7 @@ private static void AlterTablePersonalBestAttempts(SQLiteConnection connection, transferDataCmd.ExecuteNonQuery(); } - Logger.Debug("Transferred data from {0} to new_{1}", tableName, tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Transferred data from {0} to new_{1}", tableName, tableName); // 6. Drop the old table X: DROP TABLE X. // Drop the old table X @@ -14103,7 +14709,7 @@ private static void AlterTablePersonalBestAttempts(SQLiteConnection connection, dropTable.ExecuteNonQuery(); } - Logger.Debug("Deleted table {0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Deleted table {0}", tableName); // 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. // Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X @@ -14112,7 +14718,7 @@ private static void AlterTablePersonalBestAttempts(SQLiteConnection connection, renameTable.ExecuteNonQuery(); } - Logger.Debug("Renamed new_{0} to {1}", tableName, tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Renamed new_{0} to {1}", tableName, tableName); // 8. Use CREATE INDEX, CREATE TRIGGER, and CREATE VIEW to reconstruct indexes, triggers, and views associated with table X. Perhaps use the old format of the triggers, indexes, and views saved from step 3 above as a guide, making changes as appropriate for the alteration. // Recreate indexes, triggers, and views associated with the original table "QuestAttempts". @@ -14133,7 +14739,7 @@ private static void AlterTablePersonalBestAttempts(SQLiteConnection connection, { // Roll back the transaction if any errors occur Logger.Error(ex, "Could not alter table {0}", tableName); - LoggingService.WriteCrashLog(ex, string.Format("Could not alter table {0}", tableName)); + LoggingService.WriteCrashLog(ex, string.Format(CultureInfo.InvariantCulture, "Could not alter table {0}", tableName)); } } @@ -14152,7 +14758,7 @@ private static void AlterTableQuestAttempts(SQLiteConnection connection, string createTable.ExecuteNonQuery(); } - Logger.Debug("Created table if not exists new_{0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Created table if not exists new_{0}", tableName); // 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. // Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X @@ -14174,7 +14780,7 @@ private static void AlterTableQuestAttempts(SQLiteConnection connection, string dropTable.ExecuteNonQuery(); } - Logger.Debug("Deleted table {0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Deleted table {0}", tableName); // 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. // Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X @@ -14204,7 +14810,7 @@ private static void AlterTableQuestAttempts(SQLiteConnection connection, string { // Roll back the transaction if any errors occur Logger.Error(ex, "Could not alter table {0}", tableName); - LoggingService.WriteCrashLog(ex, string.Format("Could not alter table {0}", tableName)); + LoggingService.WriteCrashLog(ex, string.Format(CultureInfo.InvariantCulture, "Could not alter table {0}", tableName)); } } @@ -14251,14 +14857,14 @@ private static void AlterTableGameFolder(SQLiteConnection connection, string new createTable.ExecuteNonQuery(); } - Logger.Debug("Created table if not exists new_{0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Created table if not exists new_{0}", tableName); // 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. // Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X var countQuery = "SELECT COUNT(*) FROM GameFolder"; using (var command = new SQLiteCommand(countQuery, connection)) { - var rowCount = Convert.ToInt32(command.ExecuteScalar()); + var rowCount = Convert.ToInt32(command.ExecuteScalar(), CultureInfo.InvariantCulture); Logger.Debug("Inserting default values into new_GameFolder"); @@ -14306,7 +14912,7 @@ WHERE EXISTS (SELECT 1 FROM GameFolder WHERE GameFolder.GameFolderID = new_GameF dropTable.ExecuteNonQuery(); } - Logger.Debug("Deleted table {0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Deleted table {0}", tableName); // 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. // Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X @@ -14382,7 +14988,7 @@ WHERE EXISTS (SELECT 1 FROM GameFolder WHERE GameFolder.GameFolderID = new_GameF } } - Logger.Debug("Views affected: {0}", viewSqlsModified.Count); + Logger.Debug(CultureInfo.InvariantCulture, "Views affected: {0}", viewSqlsModified.Count); Logger.Info(CultureInfo.InvariantCulture, "Altered table {0} successfully", tableName); } @@ -14390,7 +14996,7 @@ WHERE EXISTS (SELECT 1 FROM GameFolder WHERE GameFolder.GameFolderID = new_GameF { // Roll back the transaction if any errors occur Logger.Error(ex, "Could not alter table {0}", tableName); - LoggingService.WriteCrashLog(ex, string.Format("Could not alter table {0}", tableName)); + LoggingService.WriteCrashLog(ex, string.Format(CultureInfo.InvariantCulture, "Could not alter table {0}", tableName)); } } @@ -14441,7 +15047,7 @@ private static void AlterTableSchema(SQLiteConnection connection, string tableNa createTable.ExecuteNonQuery(); } - Logger.Debug("Created table new_{0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Created table new_{0}", tableName); // 5. Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X. // Transfer content from X into new_X using a statement like: INSERT INTO new_X SELECT ... FROM X @@ -14460,7 +15066,7 @@ private static void AlterTableSchema(SQLiteConnection connection, string tableNa dropTable.ExecuteNonQuery(); } - Logger.Debug("Deleted table {0}", tableName); + Logger.Debug(CultureInfo.InvariantCulture, "Deleted table {0}", tableName); // 7. Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X. // Change the name of new_X to X using: ALTER TABLE new_X RENAME TO X @@ -14536,7 +15142,7 @@ private static void AlterTableSchema(SQLiteConnection connection, string tableNa } } - Logger.Debug("Views affected: {0}", viewSqlsModified.Count); + Logger.Debug(CultureInfo.InvariantCulture, "Views affected: {0}", viewSqlsModified.Count); Logger.Info(CultureInfo.InvariantCulture, "Altered table {0} successfully", tableName); } @@ -14544,7 +15150,7 @@ private static void AlterTableSchema(SQLiteConnection connection, string tableNa { // Roll back the transaction if any errors occur Logger.Error(ex, "Could not alter table {0}", tableName); - LoggingService.WriteCrashLog(ex, string.Format("Could not alter table {0}", tableName)); + LoggingService.WriteCrashLog(ex, string.Format(CultureInfo.InvariantCulture, "Could not alter table {0}", tableName)); } } diff --git a/MHFZ_Overlay/Services/DiscordService.cs b/MHFZ_Overlay/Services/DiscordService.cs index fba748a5..4da27288 100644 --- a/MHFZ_Overlay/Services/DiscordService.cs +++ b/MHFZ_Overlay/Services/DiscordService.cs @@ -7,11 +7,13 @@ namespace MHFZ_Overlay.Services; using System; using System.Diagnostics; using System.Globalization; +using System.Windows; using DiscordRPC; using EZlion.Mapper; using MHFZ_Overlay; using MHFZ_Overlay.Models.Collections; using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; /// /// Handles the Discord Rich Presence. Should not operate if the user is not enabling it. @@ -35,8 +37,8 @@ public sealed class DiscordService // sky corridor prologue: 21729 // raviente 62105 // raviente carve 62108 - //violent raviente 62101 - //violent carve 62104 + // violent raviente 62101 + // violent carve 62104 // berserk slay practice 55796 // berserk support practice 1 55802 // berserk support practice 2 55803 @@ -216,13 +218,13 @@ public void UpdateDiscordRPC(DataLoader dataLoader) } // TODO also need to handle the other fields lengths - if (string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}", this.GetPartySize(dataLoader), GetQuestState(dataLoader), GetCaravanScore(dataLoader), dataLoader.Model.GetOverlayModeForRPC(), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID()), GetGameMode(dataLoader.IsHighGradeEdition)).Length >= 95) + if (string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}", dataLoader.Model.GetOverlayModeForRPC(), this.GetPartySize(dataLoader), GetQuestState(dataLoader), GetCaravanScore(dataLoader), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID()), GetGameMode(dataLoader.IsHighGradeEdition)).Length >= 95) { - PresenceTemplate.Details = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", GetQuestState(dataLoader), dataLoader.Model.GetOverlayModeForRPC(), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID())); + PresenceTemplate.Details = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", dataLoader.Model.GetOverlayModeForRPC(), GetQuestState(dataLoader), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID())); } else { - PresenceTemplate.Details = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}", this.GetPartySize(dataLoader), GetQuestState(dataLoader), GetCaravanScore(dataLoader), dataLoader.Model.GetOverlayModeForRPC(), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID()), GetGameMode(dataLoader.IsHighGradeEdition)); + PresenceTemplate.Details = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}", dataLoader.Model.GetOverlayModeForRPC(), this.GetPartySize(dataLoader), GetQuestState(dataLoader), GetCaravanScore(dataLoader), dataLoader.Model.GetAreaName(dataLoader.Model.AreaID()), GetGameMode(dataLoader.IsHighGradeEdition)); } var stateString = string.Empty; @@ -289,7 +291,7 @@ public void UpdateDiscordRPC(DataLoader dataLoader) } else { - stateString = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6} | True Raw: {7} (Max {8}) | Hits: {9}", ViewModels.Windows.AddressModel.GetQuestNameFromID(dataLoader.Model.QuestID()), ViewModels.Windows.AddressModel.GetObjectiveNameFromID(dataLoader.Model.ObjectiveType()), string.Empty, dataLoader.Model.GetObjective1Quantity(), dataLoader.Model.GetRankNameFromID(dataLoader.Model.RankBand()), dataLoader.Model.GetStarGrade(), dataLoader.Model.GetRealMonsterName(), dataLoader.Model.ATK, dataLoader.Model.HighestAtk, dataLoader.Model.HitCountInt()); + stateString = string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6} | True Raw: {7} (Max {8}) | Hits: {9}", ViewModels.Windows.AddressModel.GetQuestNameFromID(dataLoader.Model.QuestID()), ViewModels.Windows.AddressModel.GetObjectiveNameFromID(dataLoader.Model.ObjectiveType()), dataLoader.Model.GetObjective1Quantity(), dataLoader.Model.GetRankNameFromID(dataLoader.Model.RankBand()), GetQuestToggleMode(dataLoader.Model.QuestToggleMonsterMode()).TrimStart(), dataLoader.Model.GetStarGrade(), dataLoader.Model.GetRealMonsterName(), dataLoader.Model.ATK, dataLoader.Model.HighestAtk, dataLoader.Model.HitCountInt()); PresenceTemplate.State = stateString.Length <= MaxDiscordRPCStringLength ? stateString : string.Concat(stateString.AsSpan(0, MaxDiscordRPCStringLength - 3), "..."); } @@ -1232,7 +1234,7 @@ private static string GetQuestInformation(DataLoader dataLoader) } else { - return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5} | ", ViewModels.Windows.AddressModel.GetObjectiveNameFromID(dataLoader.Model.ObjectiveType(), true), string.Empty, dataLoader.Model.GetObjective1Quantity(true), dataLoader.Model.GetRankNameFromID(dataLoader.Model.RankBand(), true), dataLoader.Model.GetStarGrade(true), dataLoader.Model.GetRealMonsterName(true)); + return string.Format(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}{6} | ", ViewModels.Windows.AddressModel.GetObjectiveNameFromID(dataLoader.Model.ObjectiveType(), true), string.Empty, dataLoader.Model.GetObjective1Quantity(true), dataLoader.Model.GetRankNameFromID(dataLoader.Model.RankBand(), true), GetQuestToggleMode(dataLoader.Model.QuestToggleMonsterMode()), dataLoader.Model.GetStarGrade(true), dataLoader.Model.GetRealMonsterName(true)); } } } @@ -1258,6 +1260,17 @@ private string GetPartySize(DataLoader dataLoader) } } + private static string GetQuestToggleMode(int option) + { + return option switch + { + (int)QuestToggleMonsterModeOption.Normal => string.Empty, + (int)QuestToggleMonsterModeOption.Hardcore => " HC ", + (int)QuestToggleMonsterModeOption.Unlimited => " UL ", + _ => string.Empty, + }; + } + private static int GetPartySizeMax(DataLoader dataLoader) { if (dataLoader.Model.PartySize() >= dataLoader.Model.PartySizeMax()) diff --git a/MHFZ_Overlay/Services/FileService.cs b/MHFZ_Overlay/Services/FileService.cs index 3761c1f6..ff0a9ff9 100644 --- a/MHFZ_Overlay/Services/FileService.cs +++ b/MHFZ_Overlay/Services/FileService.cs @@ -600,7 +600,13 @@ public static void CopyFileToDestination(string file, string destination, bool o Original file: {1} -Destination: {2}", logMessage, file, destination), Messages.InfoTitle, MessageBoxButton.OK, MessageBoxImage.Information); +Destination: {2}", + logMessage, + file, + destination), + Messages.InfoTitle, + MessageBoxButton.OK, + MessageBoxImage.Information); } } @@ -682,7 +688,7 @@ public static bool CreateFileIfNotExists(string path, string logMessage) } else { - Logger.Info("File does exist, canceling creation: {0}", path); + Logger.Info(CultureInfo.InvariantCulture, "File does exist, canceling creation: {0}", path); } return doesExist; diff --git a/MHFZ_Overlay/Services/GachaService.cs b/MHFZ_Overlay/Services/GachaService.cs index 120169ff..56526114 100644 --- a/MHFZ_Overlay/Services/GachaService.cs +++ b/MHFZ_Overlay/Services/GachaService.cs @@ -3,6 +3,6 @@ // found in the LICENSE file. namespace MHFZ_Overlay.Services; -internal class GachaService +internal sealed class GachaService { } diff --git a/MHFZ_Overlay/Services/GauntletService.cs b/MHFZ_Overlay/Services/GauntletService.cs index 8535d780..674ccf8c 100644 --- a/MHFZ_Overlay/Services/GauntletService.cs +++ b/MHFZ_Overlay/Services/GauntletService.cs @@ -3,6 +3,6 @@ // found in the LICENSE file. namespace MHFZ_Overlay.Services; -internal class GauntletService +internal sealed class GauntletService { } diff --git a/MHFZ_Overlay/Services/OverlaySettingsService.cs b/MHFZ_Overlay/Services/OverlaySettingsService.cs index 8c96e8d5..9ea4f2d1 100644 --- a/MHFZ_Overlay/Services/OverlaySettingsService.cs +++ b/MHFZ_Overlay/Services/OverlaySettingsService.cs @@ -7,6 +7,7 @@ namespace MHFZ_Overlay.Services; using System.Diagnostics; using System.Globalization; using MHFZ_Overlay; +using MHFZ_Overlay.Models.Structures; public sealed class OverlaySettingsService { @@ -16,15 +17,6 @@ public sealed class OverlaySettingsService private OverlaySettingsService() => Logger.Info($"Service initialized"); - public enum ConfigurationPreset - { - None, - Speedrun, - Zen, - HPOnly, - All, - } - public static OverlaySettingsService GetInstance() { if (instance == null) diff --git a/MHFZ_Overlay/Services/TimeService.cs b/MHFZ_Overlay/Services/TimeService.cs new file mode 100644 index 00000000..f1bb1cb5 --- /dev/null +++ b/MHFZ_Overlay/Services/TimeService.cs @@ -0,0 +1,199 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Services; + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services.Contracts; +using NLog; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock; + +/// +/// A service for doing time and date manipulation. Consult the benchmarks project for the performance. +/// +public static class TimeService +{ + private static readonly Logger LoggerInstance = LogManager.GetCurrentClassLogger(); + + private static double GetFramesFromTimeSpan(TimeSpan time) + { + return TimeSpan.FromSeconds(time.TotalSeconds * (double)Numbers.FramesPerSecond).TotalSeconds * (double)Numbers.FramesPerSecond; + } + + private static TimeSpan GetTimeSpanFromFrames(decimal frames) + { + return TimeSpan.FromSeconds((double)frames / (double)Numbers.FramesPerSecond); + } + + public static string GetTimeLeftPercent(decimal timeDefInt, decimal timeInt, bool isDure) + { + if (timeDefInt < timeInt || timeDefInt <= 0) + { + return " (?)"; + } + else + { + return string.Format(CultureInfo.InvariantCulture, " ({0:0}%)", timeInt / timeDefInt * 100.0M); + } + } + + public static decimal GetTimeValue(TimerMode mode, decimal timeDefInt, decimal timeInt) + { + decimal time; + + if (mode == TimerMode.Elapsed) + { + time = timeDefInt - timeInt; + } + else // default to Time Left mode + { + time = timeInt; + } + + return time; + } + + /// + /// Test the timer methods for equality up until the specified max time in frames. + /// + /// + /// The string where the inequality happened. + public static string TestTimerMethods(decimal timeDefInt) + { + decimal timeInt = timeDefInt; + var maxTime = TimeSpan.FromSeconds((double)(timeDefInt / Numbers.FramesPerSecond)); + string timer1Result = string.Empty; + string timer2Result = string.Empty; + string timer3Result = string.Empty; + + for (decimal i = timeInt; i >= -timeDefInt; i--) + { + timer1Result = StringBuilderTimer(timeInt, TimerFormat.MinutesSecondsMilliseconds, true, timeDefInt, true, GetTimeLeftPercent(timeDefInt, timeInt, true), TimerMode.Elapsed); + timer2Result = TimeSpanTimer(timeInt, TimerFormat.MinutesSecondsMilliseconds, true, timeDefInt, true, GetTimeLeftPercent(timeDefInt, timeInt, true), TimerMode.Elapsed); + timer3Result = SimpleTimer(timeInt, TimerFormat.MinutesSecondsMilliseconds, true, timeDefInt, true, GetTimeLeftPercent(timeDefInt, timeInt, true), TimerMode.Elapsed); + + if (timer1Result != timer2Result || timer3Result != timer1Result || timer3Result != timer2Result) + { + return @$"timeDefInt: {timeDefInt} ({maxTime}) | timeInt: {timeInt} +StringBuilder: {timer1Result} +TimeSpan: {timer2Result} +Simple: {timer3Result}"; + } + + timeInt--; + } + + return @$"No inequalities found. + +timeDefInt: {timeDefInt} ({maxTime}) | timeInt: {timeInt} +StringBuilder: {timer1Result} +TimeSpan: {timer2Result} +Simple: {timer3Result}"; + } + + private static string SimpleTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + // TODO wrong conditionals for timeint >= timedefint? + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? Numbers.FramesPerSecond : 1; + decimal milliseconds = time / framesPerSecond * 1000; + decimal totalMinutes = Math.Floor(milliseconds / 60000); + decimal minutes = totalMinutes >= 60 ? totalMinutes : Math.Floor(milliseconds / 60000); + decimal seconds = Math.Floor((milliseconds - (minutes * 60000)) / 1000); + decimal remainingMilliseconds = milliseconds - (minutes * 60000) - (seconds * 1000); + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + return timerFormat switch + { + TimerFormat.MinutesSeconds => $"{minutes:00}:{seconds:00}" + timeLeftPercent, + TimerFormat.MinutesSecondsMilliseconds => $"{minutes:00}:{seconds:00}.{remainingMilliseconds:000}" + timeLeftPercent, + _ => $"{minutes:00}:{seconds:00}.{remainingMilliseconds:000}" + timeLeftPercent, + }; + } + + private static string StringBuilderTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? Numbers.FramesPerSecond : 1; + decimal totalSeconds = time / framesPerSecond; + decimal totalMinutes = Math.Floor(totalSeconds / 60); + decimal minutes = totalMinutes >= 60 ? totalMinutes : Math.Floor(totalSeconds / 60); + decimal seconds = Math.Floor(totalSeconds % 60); + decimal milliseconds = Math.Round((time % framesPerSecond) * (1000M / framesPerSecond)); + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + StringBuilder sb = new StringBuilder(); + switch (timerFormat) + { + default: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", minutes, seconds, milliseconds); + break; + case TimerFormat.MinutesSeconds: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}", minutes, seconds); + break; + case TimerFormat.MinutesSecondsMilliseconds: + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", minutes, seconds, milliseconds); + break; + } + + sb.Append(timeLeftPercent); + return sb.ToString(); + } + + private static string TimeSpanTimer(decimal timeInt, TimerFormat timerFormat, bool isFrames = true, decimal timeDefInt = 0, bool timeLeftPercentShown = false, string timeLeftPercentNumber = "", TimerMode timerMode = TimerMode.Elapsed) + { + decimal time = timerMode == TimerMode.Elapsed && timeInt <= timeDefInt ? time = timeDefInt - timeInt : time = timeInt; + decimal framesPerSecond = isFrames ? Numbers.FramesPerSecond : 1; + decimal timeInSeconds = time / framesPerSecond; + TimeSpan timeInSecondsSpan = TimeSpan.FromSeconds((double)timeInSeconds); + int roundedMilliseconds = (int)(Math.Round(timeInSecondsSpan.TotalMilliseconds) % 1000); + var totalMinutes = Math.Floor(timeInSecondsSpan.TotalSeconds / 60); + var minutes = totalMinutes >= 60 ? totalMinutes : timeInSecondsSpan.Minutes; + var timeLeftPercent = timeLeftPercentShown ? timeLeftPercentNumber : string.Empty; + + // Format the TimeSpan object as a string + return timerFormat switch + { + TimerFormat.MinutesSeconds => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}" + timeLeftPercent, + TimerFormat.MinutesSecondsMilliseconds => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}.{roundedMilliseconds:000}" + timeLeftPercent, + _ => $"{minutes:00}:{timeInSecondsSpan.Seconds:00}.{roundedMilliseconds:000}" + timeLeftPercent, + }; + } + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + public static string GetMinutesSecondsFromSeconds(double seconds) => TimeSpanTimer((long)seconds, TimerFormat.MinutesSeconds, false); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + public static string GetMinutesSecondsFromFrames(double frames) => TimeSpanTimer((long)frames, TimerFormat.MinutesSeconds); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + public static string GetMinutesSecondsMillisecondsFromFrames(double frames) => TimeSpanTimer((long)frames, TimerFormat.MinutesSecondsMilliseconds); + + /// + /// Gets the elapsed time in the desired format. + /// + /// + /// + public static string GetMinutesSecondsMillisecondsFromFrames(long frames) => TimeSpanTimer(frames, TimerFormat.MinutesSecondsMilliseconds); +} diff --git a/MHFZ_Overlay/Settings.Designer.cs b/MHFZ_Overlay/Settings.Designer.cs index 6231e557..590a8e55 100644 --- a/MHFZ_Overlay/Settings.Designer.cs +++ b/MHFZ_Overlay/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace MHFZ_Overlay { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.7.0.0")] public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -2749,7 +2749,7 @@ public double SessionTimeY { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("True")] + [global::System.Configuration.DefaultSettingValueAttribute("False")] public bool ShowDiscordRPCOverlayMode { get { return ((bool)(this["ShowDiscordRPCOverlayMode"])); @@ -3802,5 +3802,149 @@ public bool BingoProgressShown { this["BingoProgressShown"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0.5")] + public float VolumeMain { + get { + return ((float)(this["VolumeMain"])); + } + set { + this["VolumeMain"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeAchievementUnlock { + get { + return ((float)(this["VolumeAchievementUnlock"])); + } + set { + this["VolumeAchievementUnlock"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeChallengeUnlock { + get { + return ((float)(this["VolumeChallengeUnlock"])); + } + set { + this["VolumeChallengeUnlock"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeChallengeStart { + get { + return ((float)(this["VolumeChallengeStart"])); + } + set { + this["VolumeChallengeStart"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeHover { + get { + return ((float)(this["VolumeHover"])); + } + set { + this["VolumeHover"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeGachaUnlock { + get { + return ((float)(this["VolumeGachaUnlock"])); + } + set { + this["VolumeGachaUnlock"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeGachaTrial { + get { + return ((float)(this["VolumeGachaTrial"])); + } + set { + this["VolumeGachaTrial"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeSelect { + get { + return ((float)(this["VolumeSelect"])); + } + set { + this["VolumeSelect"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public float VolumeGachaRare { + get { + return ((float)(this["VolumeGachaRare"])); + } + set { + this["VolumeGachaRare"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Final")] + public string OverlayWatermarkMode { + get { + return ((string)(this["OverlayWatermarkMode"])); + } + set { + this["OverlayWatermarkMode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Automatic")] + public string DiscordOverlayMode { + get { + return ((string)(this["DiscordOverlayMode"])); + } + set { + this["DiscordOverlayMode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool QuestToggleMonsterModeShown { + get { + return ((bool)(this["QuestToggleMonsterModeShown"])); + } + set { + this["QuestToggleMonsterModeShown"] = value; + } + } } } diff --git a/MHFZ_Overlay/Settings.settings b/MHFZ_Overlay/Settings.settings index 4ecbc3e9..e0a8e746 100644 --- a/MHFZ_Overlay/Settings.settings +++ b/MHFZ_Overlay/Settings.settings @@ -684,7 +684,7 @@ 180 - True + False False @@ -947,5 +947,41 @@ False + + 0.5 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Final + + + Automatic + + + True + \ No newline at end of file diff --git a/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs b/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs index bac15996..59f5d364 100644 --- a/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs +++ b/MHFZ_Overlay/ViewModels/Windows/AddressModel.cs @@ -25,10 +25,13 @@ namespace MHFZ_Overlay.ViewModels.Windows; using MHFZ_Overlay.Models; using MHFZ_Overlay.Models.Collections; using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Structures; using MHFZ_Overlay.Services; +using MHFZ_Overlay.Services.Converter; using RESTCountries.NET.Models; using RESTCountries.NET.Services; using SkiaSharp; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock; using Application = System.Windows.Application; /// @@ -1255,6 +1258,24 @@ 21747 or public abstract int PartnyaBagItem10Qty(); + /// + /// Normal/HC/UL. Set at quest counter option selection. + /// + /// + public abstract int QuestToggleMonsterMode(); + + /// + /// [] Not Done + /// [X] Done + /// [O] WIP + /// [] Prayer gems, + /// [] bento, + /// [] sharpness table, + /// [] pvp, + /// [] zenith in road, guild pugi, gear rarity colors. + /// [] Database would store prayer gems, bento, sharpness table, pvp, guild pugi. Should i use separate table? + /// + public bool HasMonster2 { get @@ -1266,12 +1287,12 @@ public bool HasMonster2 if (this.CaravanOverride()) { - return (this.CaravanMonster2ID() > 0 && this.Monster2HPInt() != 0 && this.GetNotRoad()) || this.Configuring; + return this.ShowHPBar(this.CaravanMonster2ID(), this.Monster2HPInt()) && this.GetNotRoad(); } else { // road check since the 2nd choice is used as the monster #1 - return (this.LargeMonster2ID() > 0 && this.Monster2HPInt() != 0 && this.GetNotRoad()) || this.Configuring; + return this.ShowHPBar(this.LargeMonster2ID(), this.Monster2HPInt()) && this.GetNotRoad(); } } } @@ -1405,7 +1426,7 @@ public static bool ShowOverlayStatIcon /// public static string GetQuestNameFromID(int id) { - if (id > 63421) + if (id > 63421 && DiscordService.ShowDiscordQuestNames()) { return $"Custom Quest {id}"; } @@ -1490,12 +1511,76 @@ public string IsInLauncher() } } + public string GetOverlayModeForStorage() + { + return this.GetOverlayMode() switch + { + OverlayMode.Unknown => "Unknown", + OverlayMode.Standard => "Standard", + OverlayMode.Configuring => "Configuring", + OverlayMode.ClosedGame => "Closed Game", + OverlayMode.Launcher => "Launcher", + OverlayMode.NoGame => "No Game", + OverlayMode.MainMenu => "Main Menu", + OverlayMode.WorldSelect => "World Select", + OverlayMode.TimeAttack => "Time Attack", + OverlayMode.FreestyleSecretTech => "Freestyle w/ Secret Tech", + OverlayMode.Freestyle => "Freestyle No Secret Tech", + OverlayMode.Zen => "Zen", + _ => "Not Found", + }; + } + + public string GetFinalOverlayMode() + { + if ((OverlayModeDictionary.Count == 2 && OverlayModeDictionary.Last().Value == "Standard") || + (OverlayModeDictionary.Count == 1 && OverlayModeDictionary.First().Value == "Standard") || + OverlayModeDictionary.Count > 2 || OverlayModeDictionary.Count == 0) + { + return "Standard+"; + } + else + { + // TODO: test + if (OverlayModeDictionary.Count == 2 && OverlayModeDictionary.First().Value == "Standard") + { + return OverlayModeDictionary.Last().Value + "+"; + } + else + { + return OverlayModeDictionary.First().Value + "+"; + } + } + } + public string GetOverlayModeForRPC() { var s = (Settings)Application.Current.TryFindResource("Settings"); if (s.ShowDiscordRPCOverlayMode) { - return this.GetOverlayMode(); + if (s.DiscordOverlayMode == "Final" || (s.DiscordOverlayMode == "Automatic" && s.OverlayWatermarkMode == "Final")) + { + return $"{GetFinalOverlayMode()} | "; + } + else + { + return this.GetOverlayMode() switch + { + OverlayMode.Standard => "Standard | ", + OverlayMode.Configuring => "Configuring | ", + OverlayMode.ClosedGame => "Closed Game | ", + OverlayMode.Launcher => "Launcher | ", + OverlayMode.NoGame => "Game not found | ", + OverlayMode.MainMenu => "Main menu | ", + OverlayMode.WorldSelect => "World Select | ", + OverlayMode.TimeAttack => "Time Attack | ", + OverlayMode.FreestyleSecretTech => "Freestyle Secret Tech | ", + OverlayMode.Freestyle => "Freestyle | ", // TODO rename? + OverlayMode.Zen => "Zen | ", + _ => string.Empty, + + }; + } } else { @@ -1507,14 +1592,14 @@ public string GetOverlayModeForRPC() /// Gets the overlay mode. /// /// - public string GetOverlayMode() + public OverlayMode GetOverlayMode() { var s = (Settings)Application.Current.TryFindResource("Settings"); var playerAtk = 0; var success = int.TryParse(this.ATK, NumberStyles.Number, CultureInfo.InvariantCulture, out var playerTrueRaw); if (!success) { - LoggerInstance.Warn("Could not parse player true raw as integer: {0}", this.ATK); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse player true raw as integer: {0}", this.ATK); } else { @@ -1523,27 +1608,27 @@ public string GetOverlayMode() if (this.Configuring) { - return "(Configuring) "; + return OverlayMode.Configuring; } else if (this.ClosedGame) { - return "(Closed Game) "; + return OverlayMode.ClosedGame; } else if (this.IsInLauncherBool || this.IsInLauncher() == "Yes") // works? { - return "(Launcher) "; + return OverlayMode.Launcher; } else if (this.IsInLauncher() == "NULL") { - return "(No game detected) "; + return OverlayMode.NoGame; } else if (this.QuestID() == 0 && this.AreaID() == 0 && this.BlademasterWeaponID() == 0 && this.GunnerWeaponID() == 0) { - return "(Main Menu) "; + return OverlayMode.MainMenu; } else if (this.QuestID() == 0 && this.AreaID() == 200 && this.BlademasterWeaponID() == 0 && this.GunnerWeaponID() == 0) { - return "(World Select) "; + return OverlayMode.WorldSelect; } // TODO do i need to check for road and dure? else if ( !( @@ -1579,26 +1664,26 @@ public string GetOverlayMode() || s.PersonalBestTimePercentShown || s.EnablePersonalBestPaceColor) // TODO monster 1 overview? and update README { - return string.Empty; + return OverlayMode.Standard; } else if (s.TimerInfoShown && s.EnableInputLogging && s.EnableQuestLogging && this.PartySize() == 1 && s.OverlayModeWatermarkShown) { if (this.DivaSkillUsesLeft() == 0 && this.StyleRank1() != 15 && this.StyleRank2() != 15) { - return "(Time Attack) "; + return OverlayMode.TimeAttack; } else if (this.StyleRank1() == 15 || this.StyleRank2() == 15) { - return "(Freestyle w/ Secret Tech) "; + return OverlayMode.FreestyleSecretTech; } else { - return "(Freestyle No Secret Tech) "; + return OverlayMode.Freestyle; } } else { - return "(Zen) "; + return OverlayMode.Zen; } } @@ -2114,21 +2199,6 @@ public static string GetMetadata } } - public string TimeLeftPercentNumber - { - get - { - if (this.TimeDefInt() < this.TimeInt()) - { - return "0"; - } - else - { - return string.Format(CultureInfo.InvariantCulture, " ({0:0}%)", (float)this.TimeInt() / this.TimeDefInt() * 100.0); - } - } - } - public string SharpnessPercentNumber { get @@ -2156,6 +2226,8 @@ public string SharpnessPercentNumber } } + private StringBuilder sbForTimer = new StringBuilder(); + /// /// Gets quest time in the format of mm:ss.fff. This should only be used for display purposes. /// @@ -2163,59 +2235,28 @@ public string Time { get { - int time; + // check for 1st and 2nd district dure + // TODO: find timedefint address for dures + var isDure = QuestID() == 21731 || QuestID() == 21746; + decimal timeDefInt = isDure ? Numbers.DuremudiraTimeLimitFrames : TimeDefInt(); - if (GetTimerMode() == "Time Elapsed") - { - time = this.TimeDefInt() - this.TimeInt(); - } - else if (GetTimerMode() == "Time Left") - { - time = this.TimeInt(); - } - else // default to Time Left mode - { - time = this.TimeInt(); - } + var timerMode = GetTimerMode() == "Time Elapsed" ? TimerMode.Elapsed : TimerMode.TimeLeft; + decimal time = TimeService.GetTimeValue(timerMode, timeDefInt, (decimal)TimeInt()); + decimal framesPerSecond = Numbers.FramesPerSecond; + decimal totalSeconds = time / framesPerSecond; + decimal minutes = Math.Floor(totalSeconds / 60); + decimal seconds = Math.Floor(totalSeconds % 60); + decimal milliseconds = Math.Round((time % framesPerSecond) * (1000M / framesPerSecond)); - if (time > 0) - { - if (ShowTimeLeftPercentage()) - { - this.timeLeftPercent = this.TimeLeftPercentNumber; - } - else - { - this.timeLeftPercent = string.Empty; - } + this.timeLeftPercent = ShowTimeLeftPercentage() ? TimeService.GetTimeLeftPercent(timeDefInt, TimeInt(), isDure) : string.Empty; - if (time / Numbers.FramesPerSecond / 60 < 10) - { - if ((time / Numbers.FramesPerSecond) % 60 < 10) - { - return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", time / Numbers.FramesPerSecond / 60, (time / Numbers.FramesPerSecond) % 60, (int)Math.Round((float)((time % Numbers.FramesPerSecond) * 100) / 3)) + this.timeLeftPercent; // should work fine - } - else - { - return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1}.{2:000}", time / Numbers.FramesPerSecond / 60, (time / Numbers.FramesPerSecond) % 60, (int)Math.Round((float)((time % Numbers.FramesPerSecond) * 100) / 3)) + this.timeLeftPercent; - } - } - else - { - if ((time / Numbers.FramesPerSecond) % 60 < 10) - { - return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}.{2:000}", time / Numbers.FramesPerSecond / 60, (time / Numbers.FramesPerSecond) % 60, (int)Math.Round((float)((time % Numbers.FramesPerSecond) * 100) / 3)) + this.timeLeftPercent; - } - else - { - return string.Format(CultureInfo.InvariantCulture, "{0}:{1}.{2:000}", time / Numbers.FramesPerSecond / 60, (time / Numbers.FramesPerSecond) % 60, (int)Math.Round((float)((time % Numbers.FramesPerSecond) * 100) / 3)) + this.timeLeftPercent; - } - } - } - else - { - return string.Format(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", time / Numbers.FramesPerSecond / 60, (time / Numbers.FramesPerSecond) % 60, (int)Math.Round((float)((time % Numbers.FramesPerSecond) * 100) / 3)) + this.timeLeftPercent; - } + StringBuilder sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "{0:00}:{1:00}.{2:000}", minutes, seconds, milliseconds); + sb.Append(this.timeLeftPercent); + + // MessageBox.Show(TimeService.TestTimerMethods(216_000 * 10)); // 2 hours at 30 fps + + return sb.ToString(); } } @@ -2390,7 +2431,7 @@ public string IsOnPace { // Handle the case when Monster1MaxHP cannot be parsed to an int // For example, you can return an error message, set a default value or throw an exception - LoggerInstance.Warn("Could not parse Monster1MaxHP to get pace: {0}", this.Monster1MaxHP); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse Monster1MaxHP to get pace: {0}", this.Monster1MaxHP); return "#f5e0dc"; } @@ -2470,7 +2511,7 @@ public string IsOnBestPace { // Handle the case when Monster1MaxHP cannot be parsed to an int // For example, you can return an error message, set a default value or throw an exception - LoggerInstance.Warn("Could not parse Monster1MaxHP to get best pace: {0}", this.Monster1MaxHP); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse Monster1MaxHP to get best pace: {0}", this.Monster1MaxHP); return "#f5e0dc"; } @@ -2490,7 +2531,7 @@ public string IsOnBestPace { // Handle the case when PersonalBestTimePercent cannot be parsed to an int // For example, you can return an error message, set a default value or throw an exception - LoggerInstance.Warn("Could not parse Monster1MaxHP to get best pace: {0}", this.Monster1MaxHP); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse Monster1MaxHP to get best pace: {0}", this.Monster1MaxHP); return "#f5e0dc"; } @@ -2515,21 +2556,24 @@ public string PersonalBestTimePercent { get { - if (this.PersonalBestLoaded != Messages.TimerNotLoaded && ShowPersonalBestPaceColor()) + if (this.PersonalBestLoaded != Messages.TimerNotLoaded && + ShowPersonalBestPaceColor() && + !this.PersonalBestLoaded.Contains("-")) { - const int framesPerSecond = 30; - var personalBestInFrames = (int)(framesPerSecond * TimeSpan.ParseExact(this.PersonalBestLoaded, "mm':'ss'.'fff", CultureInfo.InvariantCulture).TotalSeconds); + // TODO does this work for times over 59m? + var personalBestInFrames = (int)((int)Numbers.FramesPerSecond * TimeSpan.ParseExact(this.PersonalBestLoaded, "mm':'ss'.'fff", CultureInfo.InvariantCulture).TotalSeconds); var personalBestTimeFramesElapsed = 0; + var timeDefInt = this.QuestID() == Numbers.QuestIDFirstDistrictDuremudira || this.QuestID() == Numbers.QuestIDSecondDistrictDuremudira ? Numbers.DuremudiraTimeLimitFrames : this.TimeDefInt(); if (GetTimerMode() == "Time Left") { - personalBestTimeFramesElapsed = this.TimeDefInt() - personalBestInFrames; + personalBestTimeFramesElapsed = (int)(timeDefInt - personalBestInFrames); } else { personalBestTimeFramesElapsed = personalBestInFrames; } - var elapsedPersonalBestTimePercent = this.CalculatePersonalBestInFramesPercent(personalBestTimeFramesElapsed); + var elapsedPersonalBestTimePercent = this.CalculatePersonalBestInFramesPercent(personalBestTimeFramesElapsed, (int)timeDefInt); return string.Format(CultureInfo.InvariantCulture, "{0:0}%", elapsedPersonalBestTimePercent); } @@ -2559,7 +2603,7 @@ public string IsHighestMonsterAttackMultiplier private double HighestAttackMult { get; set; } - public double CalculatePersonalBestInFramesPercent(double personalBestInFramesElapsed) + public double CalculatePersonalBestInFramesPercent(double personalBestInFramesElapsed, int timeDefInt) { if (personalBestInFramesElapsed <= 0) { @@ -2567,7 +2611,7 @@ public double CalculatePersonalBestInFramesPercent(double personalBestInFramesEl } else { - return 100 - ((this.TimeDefInt() - this.TimeInt()) / personalBestInFramesElapsed * 100.0); + return 100 - ((timeDefInt - this.TimeInt()) / personalBestInFramesElapsed * 100.0); } } @@ -2647,7 +2691,7 @@ public string ATK } else { - LoggerInstance.Warn("Could not parse monster attack multiplier to double: {0}", this.AtkMult); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse monster attack multiplier to double: {0}", this.AtkMult); } if (decimal.TryParse(this.DefMult, NumberStyles.Any, CultureInfo.InvariantCulture, out var defMultResult)) @@ -2659,7 +2703,7 @@ public string ATK } else { - LoggerInstance.Warn("Could not parse monster defense multiplier to decimal: {0}", this.DefMult); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse monster defense multiplier to decimal: {0}", this.DefMult); } return weaponRaw.ToString(CultureInfo.InvariantCulture); @@ -4357,6 +4401,17 @@ public string DetermineMonsterBorderColor(int n) } } + public string DetermineQuestToggleMonsterModeSelected(int mode) + { + return mode switch + { + (int)QuestToggleMonsterModeOption.Normal => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + (int)QuestToggleMonsterModeOption.Hardcore => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/flame_hc.png", + (int)QuestToggleMonsterModeOption.Unlimited => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/flame_ul.png", + _ => @"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/unknown.png", + }; + } + public string Monster2HPBarColor => this.DetermineMonsterHPBarColor(2); public string Monster3HPBarColor => this.DetermineMonsterHPBarColor(3); @@ -4388,6 +4443,24 @@ public string DetermineMonsterBorderColor(int n) public string Monster4HPModeText { get; set; } = "THP"; + public string QuestToggleModeSelected => this.DetermineQuestToggleMonsterModeSelected(QuestToggleMonsterMode()); + + public string QuestToggleModeSelectedShown + { + get + { + var s = (Settings)System.Windows.Application.Current.TryFindResource("Settings"); + + if (s.QuestToggleMonsterModeShown && (this.QuestToggleMonsterMode() is (int)QuestToggleMonsterModeOption.Hardcore or (int)QuestToggleMonsterModeOption.Unlimited)) + { + return "Visible"; + } + else{ + return "Collapsed"; + } + } + } + public string CurrentMap { get @@ -4697,7 +4770,7 @@ public static string GetGender() return s.GenderExport ?? "Male"; } - public static string GetRealWeaponNameForRunID(string className, string weaponName, long styleID, long weaponID, string weaponSlot1, string weaponSlot2, string weaponSlot3) + public static string GetRealWeaponNameForRunID(string className, long styleID, long weaponID, string weaponSlot1, string weaponSlot2, string weaponSlot3) { var style = styleID switch { @@ -5425,7 +5498,7 @@ public string GetCuffsForRunID(long cuff1ID, long cuff2ID) public static string GetCaravanSkillsForRunID(int skill1, int skill2, int skill3) { - var SkillName = string.Empty; + var caravanSkillName = string.Empty; var skills = new int[] { skill1, skill2, skill3 }; for (var i = 0; i < skills.Length; i++) { @@ -5433,20 +5506,20 @@ public static string GetCaravanSkillsForRunID(int skill1, int skill2, int skill3 if (SkillCaravan.IDName.TryGetValue(skillId, out var skillName) && skillName != "None" && skillName != string.Empty) { - SkillName += skillName; + caravanSkillName += skillName; if (i != skills.Length - 1) { - SkillName += ", "; + caravanSkillName += ", "; } if (i % 5 == 4) { - SkillName += "\n"; + caravanSkillName += "\n"; } } } - return string.IsNullOrEmpty(SkillName) ? "None" : SkillName; + return string.IsNullOrEmpty(caravanSkillName) ? "None" : caravanSkillName; } /// @@ -5617,7 +5690,7 @@ public static string GetTotalGSRWeaponUnlocks() public static string GetZenithSkillsForRunID(int skill1, int skill2, int skill3, int skill4, int skill5, int skill6, int skill7) { - var SkillName = string.Empty; + var zenithSkillName = string.Empty; var skills = new int[] { skill1, skill2, skill3, skill4, skill5, skill6, skill7 }; for (var i = 0; i < skills.Length; i++) { @@ -5626,31 +5699,31 @@ public static string GetZenithSkillsForRunID(int skill1, int skill2, int skill3, { if (skillName != "None" && skillName != string.Empty) { - SkillName += skillName; + zenithSkillName += skillName; if (i != skills.Length - 1) { - SkillName += ", "; + zenithSkillName += ", "; } if (i % 5 == 4) { - SkillName += "\n"; + zenithSkillName += "\n"; } } } } - if (string.IsNullOrEmpty(SkillName)) + if (string.IsNullOrEmpty(zenithSkillName)) { return "None"; } - return SkillName; + return zenithSkillName; } public static string GetArmorSkillsForRunID(int skill1, int skill2, int skill3, int skill4, int skill5, int skill6, int skill7, int skill8, int skill9, int skill10, int skill11, int skill12, int skill13, int skill14, int skill15, int skill16, int skill17, int skill18, int skill19) { - var SkillName = string.Empty; + var armorSkillName = string.Empty; var skills = new int[] { skill1, skill2, skill3, skill4, skill5, skill6, skill7, skill8, skill9, skill10, skill11, skill12, skill13, skill14, skill15, skill16, skill17, skill18, skill19 }; for (var i = 0; i < skills.Length; i++) { @@ -5659,26 +5732,26 @@ public static string GetArmorSkillsForRunID(int skill1, int skill2, int skill3, { if (skillName != "None" && skillName != string.Empty) { - SkillName += skillName; + armorSkillName += skillName; if (i != skills.Length - 1) { - SkillName += ", "; + armorSkillName += ", "; } if (i % 5 == 4) { - SkillName += "\n"; + armorSkillName += "\n"; } } } } - if (string.IsNullOrEmpty(SkillName)) + if (string.IsNullOrEmpty(armorSkillName)) { return "None"; } - return SkillName; + return armorSkillName; } /// @@ -5742,7 +5815,7 @@ public static string GetArmorSkillWithNull(int id) public static string GetAutomaticSkillsForRunID(int skill1, int skill2, int skill3, int skill4, int skill5, int skill6) { - var SkillName = string.Empty; + var automaticSkillName = string.Empty; var skills = new int[] { skill1, skill2, skill3, skill4, skill5, skill6 }; for (var i = 0; i < skills.Length; i++) { @@ -5751,26 +5824,26 @@ public static string GetAutomaticSkillsForRunID(int skill1, int skill2, int skil { if (skillName != "None" && skillName != string.Empty) { - SkillName += skillName; + automaticSkillName += skillName; if (i != skills.Length - 1) { - SkillName += ", "; + automaticSkillName += ", "; } if (i % 5 == 4) { - SkillName += "\n"; + automaticSkillName += "\n"; } } } } - if (string.IsNullOrEmpty(SkillName)) + if (string.IsNullOrEmpty(automaticSkillName)) { return "None"; } - return SkillName; + return automaticSkillName; } /// @@ -5892,7 +5965,7 @@ public string GetGSRSkills public static string GetGSRSkillsForRunID(int skill1, int skill2) { - var SkillName = string.Empty; + var styleRankSkillName = string.Empty; var skills = new int[] { skill1, skill2 }; for (var i = 0; i < skills.Length; i++) { @@ -5901,26 +5974,26 @@ public static string GetGSRSkillsForRunID(int skill1, int skill2) { if (skillName != "None" && skillName != string.Empty) { - SkillName += skillName; + styleRankSkillName += skillName; if (i != skills.Length - 1) { - SkillName += ", "; + styleRankSkillName += ", "; } if (i % 5 == 4) { - SkillName += "\n"; + styleRankSkillName += "\n"; } } } } - if (string.IsNullOrEmpty(SkillName)) + if (string.IsNullOrEmpty(styleRankSkillName)) { return "None"; } - return SkillName; + return styleRankSkillName; } /// @@ -7485,7 +7558,7 @@ public string GenerateGearStats(long? runID = null) 0 : playerGear.GunnerWeaponID : playerGear.BlademasterWeaponID); - var realweaponName = GetRealWeaponNameForRunID(this.GetWeaponClass((int)playerGear.WeaponClassID), GetWeaponNameFromType((int)playerGear.WeaponTypeID), playerGear.StyleID, weaponID, playerGear.WeaponSlot1, playerGear.WeaponSlot2, playerGear.WeaponSlot3); + var realweaponName = GetRealWeaponNameForRunID(this.GetWeaponClass((int)playerGear.WeaponClassID), playerGear.StyleID, weaponID, playerGear.WeaponSlot1, playerGear.WeaponSlot2, playerGear.WeaponSlot3); var head = this.GetArmorHeadNameForRunID((int)playerGear.HeadID, (int)playerGear.HeadSlot1ID, (int)playerGear.HeadSlot2ID, (int)playerGear.HeadSlot3ID); var chest = this.GetArmorChestNameForRunID((int)playerGear.ChestID, (int)playerGear.ChestSlot1ID, (int)playerGear.ChestSlot2ID, (int)playerGear.ChestSlot3ID); var arms = this.GetArmorArmNameForRunID((int)playerGear.ArmsID, (int)playerGear.ArmsSlot1ID, (int)playerGear.ArmsSlot2ID, (int)playerGear.ArmsSlot3ID); @@ -7768,7 +7841,7 @@ public int CalculateTotalSmallMonstersHunted() => this.KelbiHunted() + /// Generates the compendium. /// /// - public string GenerateCompendium(DataLoader dataLoader) + public string GenerateCompendium() { var createdBy = GetFullCurrentProgramVersion(); var createdAt = DateTime.UtcNow; @@ -8018,9 +8091,9 @@ Session Duration (Highest/Lowest/Average/Median): {111} / {112} / {113} / {114} mostAttemptedQuestID, totalQuestsCompleted, totalQuestsAttempted, - GetMinutesSecondsMillisecondsFromFrames((long)questCompletionTimeElapsedAverage), - GetMinutesSecondsMillisecondsFromFrames((long)questCompletionTimeElapsedMedian), - GetMinutesSecondsMillisecondsFromFrames(totalTimeElapsedDuringQuest), + TimeService.GetMinutesSecondsMillisecondsFromFrames((long)questCompletionTimeElapsedAverage), + TimeService.GetMinutesSecondsMillisecondsFromFrames((long)questCompletionTimeElapsedMedian), + TimeService.GetMinutesSecondsMillisecondsFromFrames(totalTimeElapsedDuringQuest), mostCompletedQuestWithCarts, mostCompletedQuestWithCartsQuestID, totalCartsInQuest, @@ -9999,27 +10072,6 @@ public static string GetCurrentFeriasVersion public List WeaponUsageSeries { get; set; } = new (); - public static string StaticGetTimeElapsed(double seconds) - { - var elapsedTime = TimeSpan.FromSeconds(seconds); - var elapsedTimeString = elapsedTime.ToString("mm\\:ss", CultureInfo.InvariantCulture); - return elapsedTimeString; - } - - public static string GetTimeElapsed(double frames) - { - var elapsedTime = TimeSpan.FromSeconds(frames / Numbers.FramesPerSecond); - var elapsedTimeString = elapsedTime.ToString("mm\\:ss", CultureInfo.InvariantCulture); - return elapsedTimeString; - } - - public static string GetMinutesSecondsMillisecondsFromFrames(long frames) - { - var elapsedTime = TimeSpan.FromSeconds((double)frames / Numbers.FramesPerSecond); - var elapsedTimeString = elapsedTime.ToString(TimeFormats.MinutesSecondsMilliseconds, CultureInfo.InvariantCulture); - return elapsedTimeString; - } - // since the x axis for all of my graphs is the time elapsed in seconds in a quest, i only need 1 definition public Axis[] XAxes { get; set; } = { @@ -10029,7 +10081,7 @@ public static string GetMinutesSecondsMillisecondsFromFrames(long frames) // LiveCharts provides some common formatters // in this case we are using the currency formatter. TextSize = 12, - Labeler = (value) => StaticGetTimeElapsed(value), + Labeler = (value) => TimeService.GetMinutesSecondsFromSeconds(value), NamePaint = new SolidColorPaint(new SKColor(StaticHexColorToDecimal("#a6adc8"))), LabelsPaint = new SolidColorPaint(new SKColor(StaticHexColorToDecimal("#a6adc8"))), @@ -10706,7 +10758,9 @@ public static bool ValidateGameFolder() "Some required files are missing from the game folder. Please make sure that the game folder contains the following files: " + string.Join(", ", findFiles) + "\n" + "gameFolderFiles: " + string.Join(", ", gameFolderFiles), - Messages.WarningTitle, MessageBoxButton.OK, MessageBoxImage.Warning); + Messages.WarningTitle, + MessageBoxButton.OK, + MessageBoxImage.Warning); LoggerInstance.Warn(CultureInfo.InvariantCulture, "Missing game files"); s.EnableQuestLogging = false; s.Save(); @@ -10787,7 +10841,7 @@ public double CalculateDPS() double timeElapsedIn30FPS = this.TimeDefInt() - this.TimeInt(); // Calculate and return the DPS - return damageDealt / (timeElapsedIn30FPS / Numbers.FramesPerSecond); + return damageDealt / (timeElapsedIn30FPS / (double)Numbers.FramesPerSecond); } // TODO: gamepad @@ -10804,7 +10858,7 @@ public double CalculateTotalHitsTakenBlockedPerSecond() double timeElapsedIn30FPS = this.TimeDefInt() - this.TimeInt(); // Calculate and return the DPS - return this.TotalHitsTakenBlocked / (timeElapsedIn30FPS / Numbers.FramesPerSecond); + return this.TotalHitsTakenBlocked / (timeElapsedIn30FPS / (double)Numbers.FramesPerSecond); } public double CalculateHitsPerSecond() @@ -10813,8 +10867,8 @@ public double CalculateHitsPerSecond() { return 0; } - - return this.HitCountInt() / ((double)(this.TimeDefInt() - this.TimeInt()) / Numbers.FramesPerSecond); + // TODO is this correct? + return this.HitCountInt() / ((double)(this.TimeDefInt() - this.TimeInt()) / (double)Numbers.FramesPerSecond); } public Dictionary? DamagePerSecondDictionaryDeserealized { get; set; } @@ -10990,7 +11044,7 @@ public double CalculateHitsPerSecond() public double PreviousActionsPerMinute { get; set; } - public string PreviousOverlayMode { get; set; } = "N/A"; + public OverlayMode PreviousOverlayMode { get; set; } = OverlayMode.Unknown; public double PreviousMonster1AttackMultiplier { get; set; } @@ -11286,7 +11340,7 @@ public double GetCurrentQuestElapsedTimeInSeconds() return 0; } - return (double)(this.TimeDefInt() - this.TimeInt()) / Numbers.FramesPerSecond; + return (double)(this.TimeDefInt() - this.TimeInt()) / (double)Numbers.FramesPerSecond; } /// @@ -11879,7 +11933,7 @@ public void InsertQuestInfoIntoDictionaries() try { this.PreviousOverlayMode = this.GetOverlayMode(); - this.OverlayModeDictionary.Add(this.TimeInt(), this.GetOverlayMode()); + this.OverlayModeDictionary.Add(this.TimeInt(), GetOverlayModeForStorage()); } catch (Exception ex) { @@ -12222,7 +12276,7 @@ public void ResetQuestInfoVariables() this.PreviousPlayerStamina = 0; this.PreviousHitsPerSecond = 0; this.PreviousActionsPerMinute = 0; - this.PreviousOverlayMode = "N/A"; + this.PreviousOverlayMode = OverlayMode.Unknown; this.PreviousRoadFloor = 0; this.PreviousMonster1AttackMultiplier = 0; @@ -12317,22 +12371,7 @@ public void ClearGraphCollections() } } - public string OverlayModeWatermarkText - { - get - { - var overlayMode = this.GetOverlayMode(); - overlayMode = overlayMode.Replace("(", string.Empty); - overlayMode = overlayMode.Replace(")", string.Empty); - overlayMode = overlayMode.Trim(); - if (overlayMode == null || overlayMode == string.Empty) - { - overlayMode = "Standard"; - } - - return overlayMode; - } - } + public string OverlayModeWatermarkText => ShowOverlayModeFinalMode() ? GetFinalOverlayMode() : GetOverlayModeForStorage(); public string QuestIDBind => this.QuestID().ToString(CultureInfo.InvariantCulture); @@ -12345,6 +12384,8 @@ public string OverlayModeWatermarkText public List PlayerAchievements { get; set; } = new (); + public ReadOnlyDictionary PlayerChallenges { get; set; } + public ObservableCollection QuestLogsSearchOption { get; set; } = new ObservableCollection() { new QuestLogsOption { Name = "Compendium", IsSelected = false }, @@ -12453,6 +12494,12 @@ public static bool ShowCurrentHPPercentage() return s.EnableCurrentHPPercentage; } + public static bool ShowOverlayModeFinalMode() + { + var s = (Settings)Application.Current.TryFindResource("Settings"); + return s.OverlayWatermarkMode == "Final"; + } + public static string FindAreaIcon(int id) { var areaGroup = new List { 0 }; @@ -12481,7 +12528,7 @@ public string GetMonster1EHPPercent() { // Handle the case when Monster1HP cannot be parsed to an int // For example, you can return an error message or set some default value - LoggerInstance.Warn("Could not parse monster 1 HP to get monster 1 EHP Percent: {0}", this.Monster1HP); + LoggerInstance.Warn(CultureInfo.InvariantCulture, "Could not parse monster 1 HP to get monster 1 EHP Percent: {0}", this.Monster1HP); return " (0%)"; } diff --git a/MHFZ_Overlay/ViewModels/Windows/BingoWindowViewModel.cs b/MHFZ_Overlay/ViewModels/Windows/BingoWindowViewModel.cs new file mode 100644 index 00000000..3d4cfa07 --- /dev/null +++ b/MHFZ_Overlay/ViewModels/Windows/BingoWindowViewModel.cs @@ -0,0 +1,823 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +using CommunityToolkit.Mvvm.Input; +using System.Windows.Input; + +namespace MHFZ_Overlay.ViewModels.Windows; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using EZlion.Mapper; +using LiveChartsCore.SkiaSharpView.Painting; +using LiveChartsCore.SkiaSharpView; +using LiveChartsCore; +using MHFZ_Overlay.Models; +using MHFZ_Overlay.Models.Collections; +using MHFZ_Overlay.Models.Constant; +using MHFZ_Overlay.Models.Messengers; +using MHFZ_Overlay.Models.Structures; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.Views.Windows; +using SkiaSharp; +using Wpf.Ui.Common; +using Wpf.Ui.Controls; +using MessageBox = System.Windows.MessageBox; +using LiveChartsCore.SkiaSharpView.VisualElements; +using System.Numerics; +using LiveChartsCore.Defaults; +using LiveChartsCore.Kernel.Sketches; +using System.Windows.Markup; +using System.Collections; +using System.Runtime.CompilerServices; +using System.Windows.Media.Animation; +using LiveChartsCore.SkiaSharpView.Drawing; +using LiveChartsCore.SkiaSharpView.Extensions; +using LiveChartsCore.VisualElements; +using Xunit.Abstractions; +using System.Globalization; + +public partial class BingoWindowViewModel : ObservableRecipient, IRecipient, IRecipient +{ + private readonly Random _random = new(); + + public IEnumerable GaugeSeries { get; set; } + + public IEnumerable> VisualElements { get; set; } + + public NeedleVisual Needle { get; set; } + + private static void SetStyle( + double sectionsOuter, double sectionsWidth, PieSeries series, int order) + { + series.OuterRadiusOffset = sectionsOuter; + series.MaxRadialColumnWidth = sectionsWidth; + switch (order) + { + default: + series.Fill = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#11111b"))); + break; + case 1: + series.Fill = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#f9e2af"))); + break; + case 2: + series.Fill = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#94e2d5"))); + break; + case 3: + series.Fill = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#89b4fa"))); + break; + } + } + + [RelayCommand] + public void DoRandomChange() + { + // modifying the Value property updates and animates the chart automatically + Needle.Value = _random.Next(0, 100); + } + + private void SetGauge() + { + var sectionsOuter = 130; + var sectionsWidth = 20; + + Needle = new NeedleVisual + { + Value = 45, + Fill = new SolidColorPaint(SKColor.FromHsl(226, 64, 88, 127)), + }; + + GaugeSeries = GaugeGenerator.BuildAngularGaugeSections( + new GaugeItem(60, s => SetStyle(sectionsOuter, sectionsWidth, s, 1)), + new GaugeItem(30, s => SetStyle(sectionsOuter, sectionsWidth, s, 2)), + new GaugeItem(10, s => SetStyle(sectionsOuter, sectionsWidth, s, 3))); + + VisualElements = new VisualElement[] + { + new AngularTicksVisual + { + LabelsSize = 16, + LabelsOuterOffset = 15, + OuterOffset = 65, + TicksLength = 20, + LabelsPaint = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#cdd6f4"))), + Stroke = new SolidColorPaint(new SKColor(AddressModel.StaticHexColorToDecimal("#cdd6f4"))), + }, + Needle + }; + } + + private ObservableCollection? _observableValues { get; set; } + + private static readonly NLog.Logger LoggerInstance = NLog.LogManager.GetCurrentClassLogger(); + + public BingoWindowViewModel(SnackbarPresenter snackbarPresenter) + { + BingoWindowSnackbarPresenter = snackbarPresenter; + SetGraphs(); + SetGauge(); + } + + public void Receive(QuestIDMessage message) => OnReceivedQuestID(message); + + public void Receive(RunIDMessage message) => OnReceivedRunID(message); + + private void SetGraphs() + { + return; + // Use ObservableCollections to let the chart listen for changes (or any INotifyCollectionChanged). + _observableValues = new ObservableCollection + { + // Use the ObservableValue or ObservablePoint types to let the chart listen for property changes + // or use any INotifyPropertyChanged implementation + new ObservableValue(2), + new(5), // the ObservableValue type is redundant and inferred by the compiler (C# 9 and above) + new(4), + new(5), + new(2), + new(6), + new(6), + new(6), + new(4), + new(2), + new(3), + new(4), + new(3) + }; + + Series = new ObservableCollection + { + new LineSeries + { + Values = _observableValues, + Fill = null + } + }; + + // in the following sample notice that the type int does not implement INotifyPropertyChanged + // and our Series.Values property is of type List + // List does not implement INotifyCollectionChanged + // this means the following series is not listening for changes. + // Series.Add(new ColumnSeries { Values = new List { 2, 4, 6, 1, 7, -2 } }); + } + + [ObservableProperty] + private IEnumerable? flatCells; + + public string? PlayerBingoPointsText => $"Bingo Points: {PlayerBingoPoints}"; + + public string? WeaponRerollButtonContent => $"Reroll weapon bonuses ({WeaponRerollCost} Bingo Points)"; + + public string? CartsBuyButtonContent => $"Buy carts ({CartsCost} Bingo Points)"; + + public string? BingoStartButtonContent => IsBingoRunning ? "Cancel" : $"Start ({BingoStartCost} Bingo Points)"; + + public string? BingoStartButtonIcon => IsBingoRunning ? "ArrowCounterClockwise20" : "Play20"; + + public string? BingoStartButtonBackground => IsBingoRunning ? CatppuccinMochaColors.NameHex["Red"] : CatppuccinMochaColors.NameHex["Green"]; + + public string? ZenithBoostText => $"Zenith Boost ({ZenithGauntletItems} left)"; + + public string? SolsticeBoostText => $"Solstice Boost ({SolsticeGauntletItems} left)"; + + public string? MusouBoostText => $"Musou Boost ({MusouGauntletItems} left)"; + + public bool IsGauntletBoostMax => GauntletBoost.HasFlag(GauntletBoost.Zenith) && + GauntletBoost.HasFlag(GauntletBoost.Solstice) && + GauntletBoost.HasFlag(GauntletBoost.Musou); + + public SnackbarPresenter BingoWindowSnackbarPresenter { get; } + + public IEnumerable Difficulties + { + get + { + return Enum.GetValues(typeof(Difficulty)) + .Cast() + .Where(difficulty => difficulty != Difficulty.Unknown); + } + } + + public IEnumerable BingoLineOptions => (IEnumerable)Enum.GetValues(typeof(BingoLineColorOption)); + + private static readonly BingoService BingoServiceInstance = BingoService.GetInstance(); + + [ObservableProperty] + private BingoCell[,]? cells = new BingoCell[5, 5]; + + public ObservableCollection? Series { get; set; } + + // TODO + private void UpdateBingoBoard(int questID) + { + if (Cells == null) + { + MessageBox.Show($"Null cells"); + return; + } + + MessageBox.Show($"Updated bingo board, questID {questID}"); + + foreach (var cell in Cells) + { + if (cell == null || cell.Monster == null || cell.Monster.QuestIDs == null) + { + continue; + } + + if (cell.Monster.QuestIDs.Contains(questID)) + { + cell.IsComplete = true; + } + } + + if (CheckForBingoCompletion()) + { + // The game is over, perform any necessary actions. + MessageBox.Show($"Game over"); + StopBingo(); + } + } + + private void UpdateRunIDs(int runID) + { + RunIDs.Add(runID); + MessageBox.Show($"Updated RunIDs, runID {runID}"); + } + + partial void OnReceivedQuestIDChanged(int value) => UpdateBingoBoard(value); + + partial void OnReceivedRunIDChanged(int value) => UpdateRunIDs(value); + + partial void OnSelectedDifficultyChanged(Difficulty value) => UpdateBingoStatsFromSelectedDifficulty(value); + + partial void OnIsMusouElzelionBoostActiveChanged(bool value) => BingoStartCost = BingoServiceInstance.CalculateBingoStartCost(GauntletBoost, SelectedDifficulty, value); + + partial void OnZenithBoostCheckedChanged(bool value) + { + if (value) + { + GauntletBoost |= GauntletBoost.Zenith; + } + else + { + GauntletBoost &= ~GauntletBoost.Zenith; + } + + BingoStartCost = BingoServiceInstance.CalculateBingoStartCost(GauntletBoost, SelectedDifficulty, IsMusouElzelionBoostActive); + } + + partial void OnSolsticeBoostCheckedChanged(bool value) + { + if (value) + { + GauntletBoost |= GauntletBoost.Solstice; + } + else + { + GauntletBoost &= ~GauntletBoost.Solstice; + } + + BingoStartCost = BingoServiceInstance.CalculateBingoStartCost(GauntletBoost, SelectedDifficulty, IsMusouElzelionBoostActive); + } + + partial void OnMusouBoostCheckedChanged(bool value) + { + if (value) + { + GauntletBoost |= GauntletBoost.Musou; + } + else + { + GauntletBoost &= ~GauntletBoost.Musou; + } + + BingoStartCost = BingoServiceInstance.CalculateBingoStartCost(GauntletBoost, SelectedDifficulty, IsMusouElzelionBoostActive); + } + + private void UpdateBingoStatsFromSelectedDifficulty(Difficulty difficulty) + { + // TODO disable controls if bingo is running + if (IsBingoRunning) + { + return; + } + + BingoStartCost = BingoServiceInstance.CalculateBingoStartCost(GauntletBoost, difficulty, IsMusouElzelionBoostActive); + Carts = BingoServiceInstance.CalculateCartsAtBingoStartFromSelectedDifficulty(difficulty); + } + + + private void OnReceivedQuestID(QuestIDMessage message) + { + if (!IsBingoRunning) + { + LoggerInstance.Info("Received Quest {0} but bingo is not running.", message); + return; + } + + ReceivedQuestID = message.Value; + } + + private void OnReceivedRunID(RunIDMessage message) + { + if (!IsBingoRunning) + { + LoggerInstance.Info("Received Run {0} but bingo is not running.", message); + return; + } + + ReceivedRunID = message.Value; + } + + /// + /// The received Quest ID. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(Cells))] + private int receivedQuestID; + + /// + /// The received Run ID. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(RunIDs))] + private int receivedRunID; + + /// + /// The MonsterList field in Bingo table. Works in conjunction with Carts and WeaponTypeBonuses in order to calculate the final score. + /// + [ObservableProperty] + private List runIDs = new(); + + /// + /// Whether bingo was started. + /// + [ObservableProperty] + [NotifyCanExecuteChangedFor(nameof(WeaponRerollCommand))] + [NotifyCanExecuteChangedFor(nameof(CartsBuyCommand))] + [NotifyCanExecuteChangedFor(nameof(SetPointsCommand))] + [NotifyPropertyChangedFor(nameof(BingoNotRunning))] + [NotifyCanExecuteChangedFor(nameof(TranscendCommand))] + [NotifyCanExecuteChangedFor(nameof(ShuffleBingoCellsCommand))] + [NotifyCanExecuteChangedFor(nameof(SelectCellsOrderCommand))] + [NotifyCanExecuteChangedFor(nameof(SelectWeaponOrderCommand))] + [NotifyPropertyChangedFor(nameof(BingoStartButtonContent))] + [NotifyPropertyChangedFor(nameof(BingoStartButtonIcon))] + [NotifyPropertyChangedFor(nameof(BingoStartButtonBackground))] + private bool isBingoRunning; + + /// + /// The cost for rerolling the weapon bonuses in each bingo grid. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(WeaponRerollButtonContent))] + private long weaponRerollCost = 2; + + /// + /// The cost for buying more carts during a bingo run. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(CartsBuyButtonContent))] + private long cartsCost = 2; + + /// + /// The amount of carts. + /// + [ObservableProperty] + private long carts; + + /// + /// The cost for starting bingo. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(BingoStartButtonContent))] + private long bingoStartCost = 0; + + /// + /// The player bingo points. For view purposes only. + /// + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(PlayerBingoPointsText))] + private long playerBingoPoints = BingoServiceInstance.GetPlayerBingoPoints(); + + [ObservableProperty] + private Difficulty selectedDifficulty = Difficulty.Easy; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsGauntletBoostMax))] + private GauntletBoost gauntletBoost = GauntletBoost.None; + + [ObservableProperty] + private BingoLineColorOption selectedBingoLineOption = BingoLineColorOption.Hardest; + + [ObservableProperty] + private bool bingoExplanationShown = true; + + [ObservableProperty] + private bool zenithBoostChecked = false; + + [ObservableProperty] + private bool solsticeBoostChecked = false; + + [ObservableProperty] + private bool musouBoostChecked = false; + + [ObservableProperty] + private bool isMusouElzelionBoostActive = false; + + [ObservableProperty] + private int boardSize; + + [ObservableProperty] + private float x0; + + [ObservableProperty] + private float y0; + + [ObservableProperty] + private float x1; + + [ObservableProperty] + private float y1; + + [ObservableProperty] + private float x2; + + [ObservableProperty] + private float y2; + + [ObservableProperty] + private float x3; + + [ObservableProperty] + private float y3; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(ZenithBoostText))] + private int zenithGauntletItems; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(SolsticeBoostText))] + private int solsticeGauntletItems; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(MusouBoostText))] + private int musouGauntletItems; + + [RelayCommand] + private void StartBingo() + { + var s = (Settings)Application.Current.TryFindResource("Settings"); + if (!s.EnableQuestLogging) + { + MessageBox.Show("Enable quest logging."); + return; + } + + if (!BingoServiceInstance.SpendBingoPoints(BingoStartCost) && !IsBingoRunning) + { + var snackbar = new Snackbar(BingoWindowSnackbarPresenter) + { + Style = (Style)Application.Current.FindResource("CatppuccinMochaSnackBar"), + Title = "Not enough bingo points!", + Content = "You need more bingo points in order to start a bingo run. Try starting at Easy difficulty without any boosts.", + Icon = new SymbolIcon(SymbolRegular.ErrorCircle24), + Appearance = ControlAppearance.Danger, + Timeout = TimeSpan.FromSeconds(5), + }; + + snackbar.Show(); + return; + } + + if (!IsBingoRunning) + { + IsBingoRunning = true; + BingoExplanationShown = false; + // TODO Do you want to restart notice + // Implement your logic to start bingo here + PlayerBingoPoints -= BingoStartCost; + GenerateBoard(selectedDifficulty); + } + else + { + StopBingo(); + } + } + + public ObservableCollection CurvePoints = new ObservableCollection(); + + public ISeries[] Series2 { get; set; } = + { + new LineSeries + { + Values = new double[] { 2, 1, 3, 5, 3, 4, 6 }, + } + }; + + + [RelayCommand] + private void RefreshPoints() + { + BezierCurve curve = new BezierCurve( + new Vector2(X0, Y0), + new Vector2(X1, Y1), + new Vector2(X2, Y2), + new Vector2(X3, Y3) + ); + + BezierCurve curve2 = new BezierCurve( + new Vector2(2 * 60 * 60, 0), + new Vector2(2 * 60 * 60, 1000), + new Vector2(10 * 60, 0), + new Vector2(10 * 60, 1000) + ); + + for (float t = 0; t <= 1; t += 0.01f) + { + CurvePoints.Add(curve2.Evaluate(t)); + } + + ObservableCollection series = new(); + ObservableCollection collection = new(); + + foreach (var entry in CurvePoints) + { + collection.Add(new ObservablePoint(entry.X, entry.Y)); + } + + series.Add(new LineSeries + { + Values = collection, + LineSmoothness = .5, + GeometrySize = 0, + Stroke = new SolidColorPaint(new SKColor(0xff,0xff,0xff)) { StrokeThickness = 2 }, + Fill = new SolidColorPaint(new SKColor(0x74, 0xc7, 0xec)) { StrokeThickness = 2 }, + }); + + Series = series; + + //Series = + //{ + // new LineSeries + // { + // Values = new ChartValues(CurvePoints.Select(p => new ObservablePoint(p.X, p.Y))), + // Fill = null + // } + //}; + + //// Calculate the elapsed time (in seconds) + //float elapsedTime = 3000; + + //// Calculate the total time (in seconds) + //float totalTime = 10000; + + //// Calculate the t parameter + //float t = Math.Min(elapsedTime / totalTime, 1); + + //// Calculate the score + //Vector2 scorePoint = curve.Evaluate(t); + //var score = scorePoint.Y; + + //return score; + } + + private void GenerateBoard(Difficulty difficulty) + { + if (Cells == null) + { + MessageBox.Show($"Null cells"); + return; + } + + BoardSize = (difficulty == Difficulty.Extreme) ? 10 : 5; + Cells = new BingoCell[BoardSize, BoardSize]; + var bingoMonsterListDifficulty = difficulty == Difficulty.Extreme ? Difficulty.Hard : difficulty; + + var monsters = BingoMonsters.DifficultyBingoMonster[bingoMonsterListDifficulty].ToList(); + + PopulateBingoBoardCells(difficulty, monsters); + FlatCells = Cells.Cast(); + } + + private void PopulateBingoBoardCells(Difficulty difficulty, List monsters) + { + if (Cells == null) + { + MessageBox.Show($"Null cells"); + return; + } + + // Shuffle the list of monsters. + var rng = new Random(); + for (int i = 0; i < BoardSize; i++) + { + for (int j = 0; j < BoardSize; j++) + { + int index = rng.Next(monsters.Count); // Get a random index + BingoMonster selectedMonster = monsters[index]; // Select a monster + + Cells[i, j] = new BingoCell + { + Monster = selectedMonster, + WeaponTypeBonus = (FrontierWeaponType)rng.Next(Enum.GetValues(typeof(FrontierWeaponType)).Length), + }; + } + } + + if (difficulty == Difficulty.Extreme) + { + Cells[BoardSize / 2, BoardSize / 2] = new BingoCell + { + Monster = BingoMonsters.DifficultyBingoMonster[Difficulty.Hard].FirstOrDefault(monster => monster.Name == "Burning Freezing Elzelion"), + WeaponTypeBonus = (FrontierWeaponType)rng.Next(Enum.GetValues(typeof(FrontierWeaponType)).Length), + }; + } + } + + private void StopBingo() + { + IsBingoRunning = false; + RunIDs.Clear(); + Cells = new BingoCell[0, 0]; + BoardSize = 0; + Carts = 0; + ReceivedQuestID = 0; + ReceivedRunID = 0; + WeaponRerollCost = 2; + CartsCost = 100; + } + + /// + /// Rerolls the weapon bonuses in each cell of the bingo board. + /// + private void RerollWeaponBonuses() + { + PlayerBingoPoints -= WeaponRerollCost; + WeaponRerollCost *= 2; + // TODO upgrades affecting cost. + } + + /// + /// Buys the cart. + /// + private void BuyCart() + { + PlayerBingoPoints -= CartsCost; + CartsCost *= 2; + Carts += 1; + // TODO upgrades affecting cost. + } + + [RelayCommand(CanExecute = nameof(IsBingoNotRunning))] + private void Transcend() + { + // var AllGems = new List(); + + // Show the player the gems that they have not yet obtained + //var availableGems = AllGems.Where(g => !g.IsObtained).ToList(); + + // Allow the player to choose a gem + //var chosenGem = ChooseGem(availableGems); + + // Add the chosen gem to the player's gauntlet + // Player.Gauntlet.Gems.Add(chosenGem); + + // Reset the player's progress + } + + [RelayCommand(CanExecute = nameof(IsBingoRunning))] + private void ShuffleBingoCells() + { + + } + + [RelayCommand(CanExecute = nameof(IsBingoRunning))] + private void SelectWeaponOrder() + { + + } + + [RelayCommand(CanExecute = nameof(IsBingoRunning))] + private void SelectCellsOrder() + { + + } + + [RelayCommand(CanExecute = nameof(IsBingoRunning))] + private void WeaponReroll() + { + if (BingoServiceInstance.SpendBingoPoints(WeaponRerollCost)) + { + RerollWeaponBonuses(); + } + else + { + var snackbar = new Snackbar(BingoWindowSnackbarPresenter) + { + Style = (Style)Application.Current.FindResource("CatppuccinMochaSnackBar"), + Title = "Not enough bingo points!", + Content = "You need more bingo points in order to buy more weapon rerolls.", + Icon = new SymbolIcon(SymbolRegular.ErrorCircle24), + Appearance = ControlAppearance.Danger, + Timeout = TimeSpan.FromSeconds(5), + }; + snackbar.Show(); + } + } + + [RelayCommand(CanExecute = nameof(IsBingoRunning))] + private void CartsBuy() + { + if (BingoServiceInstance.SpendBingoPoints(WeaponRerollCost)) + { + BuyCart(); + } + else + { + var snackbar = new Snackbar(BingoWindowSnackbarPresenter) + { + Style = (Style)Application.Current.FindResource("CatppuccinMochaSnackBar"), + Title = "Not enough bingo points!", + Content = "You need more bingo points in order to buy more carts.", + Icon = new SymbolIcon(SymbolRegular.ErrorCircle24), + Appearance = ControlAppearance.Danger, + Timeout = TimeSpan.FromSeconds(5), + }; + snackbar.Show(); + } + } + + [RelayCommand(CanExecute = nameof(IsBingoNotRunning))] + private void SetPoints() + { + // PlayerBingoPoints = BingoServiceInstance.SetPlayerBingoPoints(99_999); + } + + public bool IsBingoNotRunning() + { + return !IsBingoRunning; + } + + public bool BingoNotRunning => IsBingoNotRunning(); + + private bool CheckForBingoCompletion() + { + if (Cells == null) + { + MessageBox.Show($"Null cells"); + return false; + } + + // Check each row. + for (int i = 0; i < Cells.GetLength(0); i++) + { + if (Enumerable.Range(0, Cells.GetLength(1)).All(j => Cells[i, j].IsComplete)) + { + return true; + } + } + + // Check each column. + for (int j = 0; j < Cells.GetLength(1); j++) + { + if (Enumerable.Range(0, Cells.GetLength(0)).All(i => Cells[i, j].IsComplete)) + { + return true; + } + } + + // Check the diagonal from top-left to bottom-right. + if (Enumerable.Range(0, Cells.GetLength(0)).All(i => Cells[i, i].IsComplete)) + { + return true; + } + + // Check the diagonal from top-right to bottom-left. + if (Enumerable.Range(0, Cells.GetLength(0)).All(i => Cells[i, Cells.GetLength(0) - 1 - i].IsComplete)) + { + return true; + } + + return false; + } + + // TODO + private ChallengeAncientDragonPart ChooseGem(List availableGems) + { + return new ChallengeAncientDragonPart(); + } +} diff --git a/MHFZ_Overlay/ViewModels/Windows/GachaWindowViewModel.cs b/MHFZ_Overlay/ViewModels/Windows/GachaWindowViewModel.cs new file mode 100644 index 00000000..40d43fa5 --- /dev/null +++ b/MHFZ_Overlay/ViewModels/Windows/GachaWindowViewModel.cs @@ -0,0 +1,15 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.ViewModels.Windows; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +public sealed class GachaWindowViewModel +{ +} diff --git a/MHFZ_Overlay/ViewModels/Windows/GauntletWindowViewModel.cs b/MHFZ_Overlay/ViewModels/Windows/GauntletWindowViewModel.cs new file mode 100644 index 00000000..597f08eb --- /dev/null +++ b/MHFZ_Overlay/ViewModels/Windows/GauntletWindowViewModel.cs @@ -0,0 +1,15 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.ViewModels.Windows; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +public sealed class GauntletWindowViewModel +{ +} diff --git a/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml b/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml index 68fdfe2e..f0d069a9 100644 --- a/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml +++ b/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml @@ -8,7 +8,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:customcontrols="clr-namespace:MHFZ_Overlay.Views.CustomControls" + xmlns:customcontrols="clr-namespace:MHFZ_Overlay.Views.CustomControls" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" x:Name="window"> @@ -21,11 +21,13 @@ - - + + + - - + + + diff --git a/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml.cs b/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml.cs index d6f2407a..78723e4d 100644 --- a/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml.cs +++ b/MHFZ_Overlay/Views/CustomControls/MonsterHPBar.xaml.cs @@ -10,6 +10,7 @@ namespace MHFZ_Overlay.Views.CustomControls; using System.Windows; using System.Windows.Controls; using System.Windows.Media; +using MHFZ_Overlay.Models.Constant; /// /// Interaction logic for UserControl1.xaml. @@ -103,6 +104,18 @@ public string HPMode set => this.SetValue(HPModeProperty, value); } + public string QuestToggleMode + { + get => (string)this.GetValue(QuestToggleModeProperty); + set => this.SetValue(QuestToggleModeProperty, value); + } + + public string QuestToggleModeShown + { + get => (string)this.GetValue(QuestToggleModeShownProperty); + set => this.SetValue(QuestToggleModeShownProperty, value); + } + public static readonly DependencyProperty NumCurrProperty = DependencyProperty.Register("NumCurr", typeof(int), typeof(MonsterHPBar), new PropertyMetadata(0)); @@ -110,16 +123,16 @@ public string HPMode DependencyProperty.Register("NumMax", typeof(int), typeof(MonsterHPBar), new PropertyMetadata(0)); public static readonly DependencyProperty BarColorProperty = - DependencyProperty.Register("BarColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(null)); + DependencyProperty.Register("BarColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(Brushes.Black)); public static readonly DependencyProperty BorderColorProperty = - DependencyProperty.Register("BorderColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(null)); + DependencyProperty.Register("BorderColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(Brushes.Black)); public static readonly DependencyProperty IconSourceProperty = - DependencyProperty.Register("IconSource", typeof(string), typeof(MonsterHPBar), new PropertyMetadata(string.Empty)); + DependencyProperty.Register("IconSource", typeof(string), typeof(MonsterHPBar), new PropertyMetadata(Messages.MonsterImageNotLoaded)); public static readonly DependencyProperty StrokeColorProperty = - DependencyProperty.Register("StrokeColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(null)); + DependencyProperty.Register("StrokeColor", typeof(Brush), typeof(MonsterHPBar), new PropertyMetadata(Brushes.Black)); public static readonly DependencyProperty BarTypeProperty = DependencyProperty.Register("BarType", typeof(string), typeof(MonsterHPBar), new PropertyMetadata(string.Empty)); @@ -127,6 +140,12 @@ public string HPMode public static readonly DependencyProperty HPModeProperty = DependencyProperty.Register("HPMode", typeof(string), typeof(MonsterHPBar), new PropertyMetadata(string.Empty)); + public static readonly DependencyProperty QuestToggleModeProperty = + DependencyProperty.Register("QuestToggleMode", typeof(string), typeof(MonsterHPBar), new PropertyMetadata(Messages.MonsterImageNotLoaded)); + + public static readonly DependencyProperty QuestToggleModeShownProperty = +DependencyProperty.Register("QuestToggleModeShown", typeof(string), typeof(MonsterHPBar), new PropertyMetadata("Collapsed")); + /// /// The current hp percent. /// @@ -286,6 +305,18 @@ public string HPModeText set => this.HPMode = value; } + /// + /// Gets or sets the quest toggle monster mode. + /// + /// + /// The mode. + /// + public string QuestToggleModeText + { + get => this.QuestToggleMode; + set => this.QuestToggleMode = value; + } + public static string IconShown { get diff --git a/MHFZ_Overlay/Views/CustomControls/MonsterStatusBar.xaml.cs b/MHFZ_Overlay/Views/CustomControls/MonsterStatusBar.xaml.cs index c88315b5..fc6a8366 100644 --- a/MHFZ_Overlay/Views/CustomControls/MonsterStatusBar.xaml.cs +++ b/MHFZ_Overlay/Views/CustomControls/MonsterStatusBar.xaml.cs @@ -16,9 +16,6 @@ namespace MHFZ_Overlay.Views.CustomControls; /// public partial class MonsterStatusBar : UserControl, INotifyPropertyChanged { - public static readonly DependencyProperty DescriptionProperty = - DependencyProperty.Register("Description", typeof(string), typeof(MonsterStatusBar), new PropertyMetadata(string.Empty)); - public MonsterStatusBar() { this.InitializeComponent(); @@ -97,6 +94,9 @@ public string IconSource set => this.SetValue(IconSourceProperty, value); } + public static readonly DependencyProperty DescriptionProperty = + DependencyProperty.Register("Description", typeof(string), typeof(MonsterStatusBar), new PropertyMetadata(string.Empty)); + public static readonly DependencyProperty NumCurrProperty = DependencyProperty.Register("NumCurr", typeof(int), typeof(MonsterStatusBar), new PropertyMetadata(0)); @@ -110,7 +110,7 @@ public string IconSource DependencyProperty.Register("BorderColor", typeof(Brush), typeof(MonsterStatusBar), new PropertyMetadata(null)); public static readonly DependencyProperty IconSourceProperty = - DependencyProperty.Register("IconSource", typeof(string), typeof(MonsterStatusBar), new PropertyMetadata(string.Empty)); + DependencyProperty.Register("IconSource", typeof(string), typeof(MonsterStatusBar), new PropertyMetadata(@"pack://application:,,,/MHFZ_Overlay;component/Assets/Icons/png/poison.png")); public static readonly DependencyProperty StrokeColorProperty = DependencyProperty.Register("StrokeColor", typeof(Brush), typeof(MonsterStatusBar), new PropertyMetadata(null)); diff --git a/MHFZ_Overlay/Views/Windows/BingoWindow.xaml b/MHFZ_Overlay/Views/Windows/BingoWindow.xaml new file mode 100644 index 00000000..c4ba14d9 --- /dev/null +++ b/MHFZ_Overlay/Views/Windows/BingoWindow.xaml @@ -0,0 +1,508 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MHFZ_Overlay/Views/Windows/BingoWindow.xaml.cs b/MHFZ_Overlay/Views/Windows/BingoWindow.xaml.cs new file mode 100644 index 00000000..97071123 --- /dev/null +++ b/MHFZ_Overlay/Views/Windows/BingoWindow.xaml.cs @@ -0,0 +1,52 @@ +// © 2023 The mhfz-overlay developers. +// Use of this source code is governed by a MIT license that can be +// found in the LICENSE file. + +namespace MHFZ_Overlay.Views.Windows; + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; +using MHFZ_Overlay.Services; +using MHFZ_Overlay.ViewModels.Windows; +using Wpf.Ui.Contracts; +using Wpf.Ui.Controls; +using Wpf.Ui.Services; + +/// +/// Interaction logic for Window1.xaml +/// +public partial class BingoWindow : FluentWindow +{ + public BingoWindow() + { + InitializeComponent(); + ISnackbarService snackbarService = new SnackbarService(); + + // dependency injection? + snackbarService.SetSnackbarPresenter(this.BingoWindowSnackBarPresenter); + DataContext = new BingoWindowViewModel(this.BingoWindowSnackBarPresenter); + } + + private void BingoWindowObject_Closed(object sender, EventArgs e) + { + ChallengeServiceInstance.State = Models.Structures.ChallengeState.Idle; + } + + private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); + + private static readonly ChallengeService ChallengeServiceInstance = ChallengeService.GetInstance(); +} diff --git a/MHFZ_Overlay/Views/Windows/ConfigWindow.xaml b/MHFZ_Overlay/Views/Windows/ConfigWindow.xaml index dda3e51c..7510c6a1 100644 --- a/MHFZ_Overlay/Views/Windows/ConfigWindow.xaml +++ b/MHFZ_Overlay/Views/Windows/ConfigWindow.xaml @@ -10,6 +10,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:gif="http://wpfanimatedgif.codeplex.com" xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf" + xmlns:tray="http://schemas.lepo.co/wpfui/2022/xaml/tray" d:DataContext="{d:DesignInstance Type=windows:AddressModel}" xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" @@ -53,6 +54,8 @@ + + @@ -1335,7 +1338,7 @@ -