Compare commits

..
439 Commits
Author SHA1 Message Date
Atony 2a7570f022 Adjust code that gets roles and modifiers list 2026-10-22 02:01:05 -05:00
Atony 71138c7bbe Display current role and modifiers at top 2026-10-22 01:17:21 -05:00
Atony 2993ee2bbc Resync options upon clicking start
This is necessary to prevent some pretty common option desync that tends to happen. If anyone else can optimize it further or move it somewhere else, go for it.
2026-10-21 23:37:46 -05:00
Atony 79bbebd12a Allow modifiers and roles to override chance text 2026-10-21 17:48:33 -05:00
Az 5021afeee7 improve reflection search 2026-10-18 03:04:04 -04:00
Az 6bfc49af00 rename file 2026-10-18 02:25:04 -04:00
Atony 7a4e891367 Merge branch 'dev' into xml-translations 2026-10-18 00:23:35 -05:00
Az 1667fa4541 add missing documentation 2026-10-18 00:41:39 -04:00
AS fc7a1dc831 Merge pull request #112 from All-Of-Us-Mods/options-improvements
More additions to the options system
2026-10-18 00:37:36 -04:00
Atony d90d3c0a28 Merge pull request #107 from All-Of-Us-Mods/controller-support
Controller Support
2026-10-17 23:10:47 -05:00
Atony 6e48446894 Merge branch 'dev' into controller-support 2026-10-17 21:43:49 -05:00
Atony 0224ac95bc Merge branch 'dev' into options-improvements 2026-10-17 21:43:45 -05:00
Atony 48ee06990d Add background for tab buttons 2026-10-17 21:23:17 -05:00
Atony 0ddbabadd6 Add full grid options 2026-10-17 19:39:08 -05:00
Atony 12dee96200 Add auto-alignment to the left of the guide 2026-10-17 19:10:45 -05:00
Az d8162dcb32 avoid patching indexers instead 2026-10-17 18:23:15 -04:00
Az 5106efe2ac optimisations and utilities 2026-10-17 15:52:53 -04:00
Atony dc87c0fc7f Fix conflicting Local Settings Tabs
Neil made this change fyi
2026-10-17 11:26:29 -05:00
Az 6364f8eae6 constructor parity + scrap toggle display delegate 2026-10-17 04:27:24 -04:00
Az 3e421909de Merge branch 'dev' into options-improvements 2026-10-17 04:03:19 -04:00
Az e4cb4a28df warnings begone 2026-10-17 04:02:58 -04:00
AS 0f51eee1be Merge branch 'dev' into options-improvements 2026-10-16 20:52:49 -04:00
Atony 0b85c9fe96 Add new modifiers, settings, and page buttons 2026-10-16 14:57:56 -05:00
Atony 8fa6903a8d Merge pull request #111 from idkimneil/wiki-improv
Wiki improv
2026-10-16 14:19:20 -05:00
Az ca587ebb18 optimise code + add new example option 2026-10-15 23:21:09 -04:00
Az e0db03b576 Merge branch 'dev' into controller-support 2026-10-15 21:30:54 -04:00
Az aa69fafcf1 minor changes 2026-10-15 21:16:58 -04:00
Az 5e12d1f0d6 oops, copy paste 2026-10-15 21:07:17 -04:00
Az 04b7701b79 more stuff 2026-10-15 20:46:41 -04:00
neilmjain 5c51037503 track options better 2026-10-15 18:09:27 -04:00
neilmjain da7162d60d resize option content with more than 2 columns 2026-10-15 18:03:48 -04:00
neilmjain 602665079c wire up headers to pages with more than 2 columns 2026-10-15 18:03:09 -04:00
neilmjain 39d77a301b add properties to merge groups of options on one page 2026-10-15 18:01:27 -04:00
neilmjain 5e9967606d enlarge setting content by base 2026-10-15 16:37:11 -04:00
neilmjain a7585ee2fe add setting searching by mod name 2026-10-15 16:37:00 -04:00
Az b074fdc330 order code 2026-10-15 03:37:23 -04:00
Az fb74a688c3 remove useless translation calls + order players 2026-10-15 03:28:40 -04:00
Az 7ed3944ad1 target non public getters and setters 2026-10-15 03:10:38 -04:00
Az f08a00ded7 field support 2026-10-15 02:58:01 -04:00
Az f433454fe6 add missing documentation line 2026-10-15 02:01:14 -04:00
Az a31b9e29b9 improve options system 2026-10-15 01:41:56 -04:00
Atony bcacbad0e6 Merge branch 'dev' into wiki-improv 2026-10-14 23:13:28 -05:00
Atony c1978bbb88 Add sprites for settings in wiki 2026-10-14 23:09:08 -05:00
neilmjain cac15bf07f adjust positons of tab labels 2026-10-14 22:25:20 -04:00
neilmjain 1db6a80768 add the no results found text again 2026-10-14 22:18:55 -04:00
neilmjain 06ca48c088 Fix No Results Found text overlaying other UI elements. and add No Results found message to Settings tab 2026-10-14 22:08:59 -04:00
neilmjain 7b77d0d102 use Option Headers as Dividers to Search Options Results 2026-10-14 22:08:28 -04:00
neilmjain 901503f762 fix double dummy roles being spawned on the panel 2026-10-14 22:00:23 -04:00
Az 56cc7661c7 Merge branch 'master' into dev 2026-10-14 21:04:02 -04:00
AS bceed03ac7 Merge pull request #109 from All-Of-Us-Mods/more-colors
Add variable colors
2026-10-14 21:01:35 -04:00
neilmjain 144c762ae3 Merge branch 'wiki-improv' of https://github.com/idkimneil/MiraAPI into wiki-improv 2026-10-14 20:57:13 -04:00
neilmjain 5197b90360 layer sorting by extra mods 2026-10-14 20:57:12 -04:00
neilmjain 542bf72ece Sort Pages by Priority of menus and groups 2026-10-14 20:45:01 -04:00
neilmjain 754161d439 Implement ShowInMatchInfoGuide 2026-10-14 20:44:45 -04:00
Atony 63c411754c Merge branch 'wiki-improv' of https://github.com/idkimneil/MiraAPI into pr/111 2026-10-14 19:13:54 -05:00
Atony 5e863747f7 Merge branch 'dev' into pr/111 2026-10-14 19:13:42 -05:00
neilmjain a01ff6168c Override Vanilla View to match others 2026-10-14 20:08:44 -04:00
neilmjain 75142dbcb8 rename sm properties for easier reading 2026-10-14 19:01:36 -04:00
neilmjain 6d40113ea0 fix scroller and option content clipping 2026-10-14 18:36:57 -04:00
neilmjain e3358507bf Update UI to clone Options Num Rows to display settings 2026-10-14 18:20:21 -04:00
neilmjain 5ec02eec9c add CreateAdvancedSettingsPage to print the settings 2026-10-14 17:50:45 -04:00
neilmjain 1e453cf6af add very scuffed pagination of game settings 2026-10-14 17:26:00 -04:00
CallOfCreator 2748c64db2 Fix custom controller bindings blocking unused vanilla actions 2026-10-14 16:51:37 +00:00
Az 1027e90447 minor improvements 2026-10-14 03:49:21 -04:00
Az 60537fe1a3 Merge branch 'dev' into more-colors 2026-10-14 00:49:38 -04:00
Az 50312e31c2 resolve and suppress warnings 2026-10-14 00:48:08 -04:00
Az 19dd1bb497 fix mentioned issues + add more functionality 2026-10-14 00:18:56 -04:00
Atony 51efa74811 Merge branch 'dev' into controller-support 2026-10-13 22:50:50 -05:00
neilmjain 1c8f7318d3 add no results found text to modifiers page 2026-10-13 23:04:02 -04:00
neilmjain 8e4ff7c179 add filtering/hiding to modifiers page 2026-10-13 23:00:54 -04:00
neilmjain afcb4dc180 change No Roles Enabled to No Results Found for more general meaning across search settings 2026-10-13 23:00:40 -04:00
neilmjain 98aa9dc9a8 Merge branch 'wiki-improv' of https://github.com/idkimneil/MiraAPI into wiki-improv 2026-10-13 22:56:11 -04:00
neilmjain b6414fe5a0 Filter/hide roles by search in cleaner way 2026-10-13 22:52:16 -04:00
neilmjain 6f36aa91a5 also add filtering/hiding of entries not pertaining to search 2026-10-13 22:44:20 -04:00
neilmjain 57471289e1 Scale advanced wiki page content 2026-10-13 22:43:48 -04:00
neilmjain cc4daf5d8b change maximum to 1.00 instead of 1.50 2026-10-13 22:40:58 -04:00
AS 3b336fb27c Merge branch 'dev' into more-colors 2026-10-13 22:30:16 -04:00
AS 9da879fd61 Merge pull request #108 from All-Of-Us-Mods/overlooked-behaviour
Fixes to codebase
2026-10-13 22:23:04 -04:00
Atony 503b7cec51 Merge branch 'dev' into controller-support 2026-10-13 21:06:27 -05:00
Atony 348c7c615b Merge branch 'dev' into more-colors 2026-10-13 21:06:23 -05:00
Atony a07efcd268 Merge branch 'dev' into overlooked-behaviour 2026-10-13 21:06:12 -05:00
neilmjain 613ebafa8a Add match info guide scale setting 2026-10-13 21:24:18 -04:00
Atony 7349a259a1 Add icons to all menus 2026-10-12 03:07:57 -05:00
Atony a9dabe9ff4 Merge branch 'dev' into xml-translations 2026-10-11 22:39:05 -05:00
Atony 5d354eff5e Save wiki preferences in config file 2026-10-11 18:52:29 -05:00
Atony a6dab42df3 Hide searchbox in advanced menu pane 2026-10-11 18:31:10 -05:00
Atony 67e3a6a7bc Add labels to filter, grouping, and order buttons 2026-10-11 17:33:15 -05:00
Az 3982867943 cache il2cpp type 2026-10-11 12:29:32 -04:00
Az d5b08a7145 better audio loading 2026-10-11 12:25:13 -04:00
Az 01b172d73e remove more code, actually
and also fix build error
2026-10-11 12:05:25 -04:00
Az bdf9f4c9e8 deprecate addressables loader
- decprecates the addressables loader utility
- increments the required version
2026-10-11 12:01:26 -04:00
Sameer Umar 362cfc02ff Merge pull request #110 from rewalo/wiki-preset-fixes
Wiki preset fixes
2026-10-11 00:42:13 -04:00
ReAdmin d3bce25ef6 Guard EnsurePanel and expose TriggerPreload 2026-10-08 20:49:11 -07:00
ReAdmin 317fd243b1 Show role/modifier icons on wiki pages, fix wiki softlock after rejoining, even out sort buttons 2026-10-08 20:15:16 -07:00
ReAdmin b6bddbd0df Fix duplicate entries by building entries once on first open 2026-10-08 19:16:37 -07:00
ReAdmin edadd4f350 Defer UI init to first open, add search debounce, fix advanced wiki scale 2026-10-08 19:15:04 -07:00
ReAdmin c5431f0666 Keep advanced wiki pages at local scale when parenting 2026-10-08 18:46:44 -07:00
ReAdmin ec4a5f2bae Keep the wiki open animation smooth by deferring panel work 2026-10-08 18:27:14 -07:00
ReAdmin abfbd8ee73 Reveal wiki panels progressively and preload them before first open 2026-10-08 18:16:13 -07:00
ReAdmin f0650a9841 Look up role parent mods by type instead of scanning IL2CPP objects 2026-10-08 18:16:13 -07:00
Az 43f1209c1c minor code changes 2026-10-08 01:27:24 -04:00
Az a90a0c4905 cache audio clips 2026-10-08 01:20:05 -04:00
Az 6410a0dd42 consolidate copy pasted addressables code 2026-10-08 01:02:09 -04:00
Az f5a994abc3 avoid over-allocating objects
when reading sprite sheets
2026-10-08 00:30:57 -04:00
rewalo c4a5c638f7 Merge branch 'dev' into wiki-preset-fixes 2026-10-07 03:30:16 -07:00
ReAdmin 5379f22364 Remove wiki open timing diagnostics 2026-10-07 03:28:30 -07:00
ReAdmin cb292308c4 Add temporary wiki open frame timing diagnostics 2026-10-07 03:04:16 -07:00
ReAdmin ff17c946a4 Spread wiki panel reveal over frames and show the ring immediately 2026-10-07 03:03:51 -07:00
ReAdmin 5c0e5473a5 Reveal wiki panels across frames on every open 2026-10-07 02:59:10 -07:00
ReAdmin b84c5b064c Avoid redundant panel refreshes when reopening the wiki 2026-10-07 02:53:04 -07:00
ReAdmin ffd42a4a91 Copy the original label layout onto the cloned wiki search field 2026-10-07 02:44:04 -07:00
ReAdmin b94f0aa63b Build the wiki search box from the chat text field instead of a bare TextBoxTMP 2026-10-07 02:37:48 -07:00
ReAdmin 5d9c38a503 Wire a placeholder into the wiki search box so clearing an empty box works 2026-10-07 02:32:42 -07:00
ReAdmin 9cefeebe2b Keep the loading ring sprite alive across scene loads 2026-10-07 02:24:27 -07:00
ReAdmin a88f9b6611 Give the wiki search box a caret so clearing it does not corrupt input 2026-10-07 02:24:27 -07:00
ReAdmin 034521b7b6 Create resource sprites as full rect to stop tiling warnings 2026-10-07 02:16:11 -07:00
ReAdmin 77366593f1 Fix wiki open aborting before the player list is created 2026-10-07 02:16:11 -07:00
ReAdmin 0903503782 Show a loading spinner while wiki panels are built 2026-10-07 02:08:25 -07:00
ReAdmin a297898e6e Fix wiki failing to open after clearing the search box 2026-10-07 02:03:03 -07:00
ReAdmin c5c55745fd Build wiki panels across frames and stop re-initialising them on refresh 2026-10-07 01:56:58 -07:00
ReAdmin 9e5b470ae6 Fix CoLoopWithBudget default budget being zero 2026-10-07 01:53:09 -07:00
ReAdmin d827e5d1f9 Tidy role guide sorting, refresh and layout code 2026-10-07 01:40:35 -07:00
ReAdmin b00775e868 Clean up wiki options text generation 2026-10-07 01:31:44 -07:00
ReAdmin 83722a81c8 Share advanced wiki page creation 2026-10-07 01:31:06 -07:00
ReAdmin a1d22b2fac Fix preset lookup racing mod switches during option creation 2026-10-07 01:29:13 -07:00
Atony eac86ca4eb Remove clear garbage collector functionality
Crashes instantly on x64
2026-10-05 21:02:05 -05:00
Atony e5c41913cb Merge pull request #106 from TheDavSmasher/generic-option-list
Adds a generic IModdedOptionList definition for easier typed iteration
2026-10-05 01:04:55 -05:00
Atony 3b4b6e1800 Merge branch 'dev' into generic-option-list 2026-10-05 01:04:14 -05:00
Atony 7ee4d7da96 Bump appended number for option lists 2026-10-05 01:00:01 -05:00
Atony 206b00c11f De-duplicate some code 2026-10-04 23:18:39 -05:00
Atony aec2cfcad3 Fix presets failing to save 2026-10-04 22:09:03 -05:00
Atony 0dfb65e9b6 clear redundant code 2026-10-04 18:29:34 -05:00
Atony 3cf1963414 Optimize search and filter to be "per-menu" 2026-10-04 18:29:18 -05:00
Atony 6d1557a48d Add modifier support for wiki 2026-10-04 17:17:42 -05:00
Atony 5e6645ff3b Add wiki components to modifiers 2026-10-04 11:49:19 -05:00
AS a36f010207 Merge branch 'dev' into overlooked-behaviour 2026-10-04 02:52:02 -04:00
AS 7deaf5e963 Merge branch 'dev' into more-colors 2026-10-04 02:51:44 -04:00
Atony d0138936be Clear helper warnings 2026-10-04 00:16:04 -05:00
Atony 7cb8246953 Add more locale 2026-10-03 22:47:49 -05:00
Atony 9fd5b488b8 Add wiki abilities as a list for ICustomRole 2026-10-03 22:25:37 -05:00
Atony 24407580d5 Add summarized options helper for wiki 2026-10-03 22:25:01 -05:00
Atony 15c0002395 Open Roles Wiki tab immediately 2026-10-03 19:21:50 -05:00
Atony 91c7ea8dda Toggle roles enabled message properly 2026-10-03 19:16:39 -05:00
Atony 3bcc5139f3 Fix code spacing 2026-10-03 19:10:02 -05:00
Atony b912f6ffa3 Add search box reset safeguard 2026-10-03 19:09:28 -05:00
Atony f0503a6d68 Finish search functionality 2026-10-03 18:47:31 -05:00
Atony c2e251e9c6 Change text color 2026-10-03 15:59:20 -05:00
Atony 43fa7a0a2a Add functional search box and change UI 2026-10-03 15:44:26 -05:00
Atony a27a45944c Add WIP search box 2026-10-03 03:06:33 -05:00
XtraCube 79dc3280e8 dont use il2cpp lists unless absolutely necessary! 2026-10-02 11:52:15 -04:00
Sameer Umar 5303f09627 Add language links and badges to README 2026-10-01 23:07:47 -04:00
Sameer Umar 85f8da54dd Update README with new badge and language links 2026-10-01 23:07:12 -04:00
Az 2e212dee47 bounds checks 2026-10-01 01:50:39 -04:00
Az c2c7c63b9b correct documentation 2026-09-30 19:02:40 -04:00
Atony f52c5de4cd Add wip sorting toggling
az i know there's warnings PLEASE don't slime me out i'll fix it later i promise 🙏
2026-09-30 10:31:27 -05:00
Az cd753d554b fix naming
it originally was called "VariantColor" before i switched it to "VariableColor"
2026-09-30 10:28:26 -04:00
Az f3ddf8d2ab squash final bugs 2026-09-30 10:00:26 -04:00
Atony ab7d43f18b Add vanilla button checks for Influencer 2026-09-29 20:56:03 -05:00
Atony 9f4e519b9e Update game version 2026-09-29 20:55:42 -05:00
Az 27ac201f88 fix oversights + errors 2026-09-29 16:06:49 -04:00
Az a73d1df67e optimisations + example color 2026-09-29 13:54:06 -04:00
CallOfCreator 24f71de1dd Fixed the keybind menu not appearing when using controller input 2026-09-29 15:56:34 +00:00
Az 3bf066f38c add variable color system + color spaces 2026-09-29 09:53:18 -04:00
HayashiUme 44d476dd3a Translated using Weblate (Literary Chinese (lzh_CN))
Currently translated at 52.6% (20 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/lzh_CN/
2026-09-28 13:08:53 +00:00
Az c1cd4a7d67 fix small overlooked behaviours
- fixes issue #45 where the player leave event was in a postfix, when the player character was already destroyed
- fixes #46 where modded role options did not have the same change zero behaviour as vanilla ones
2026-09-27 23:24:57 -04:00
Az 5fb60a7082 fix warnings
scav you have committed a war crime. C# =/= python
2026-09-27 16:59:17 -04:00
CallOfCreator 041c39d7ff Controller Support 2026-09-27 16:14:56 +00:00
David Higueros 2d4619f2cf Used generic interface for ModdedOptionList 2026-09-25 12:44:44 -06:00
David Higueros 1b587547f7 Added ConfigName property to IModdedOptionList to optionally rename the individual options 2026-09-25 12:43:14 -06:00
David Higueros ffe9194d54 Resolved warnings 2026-09-25 11:47:12 -06:00
Atony 3575f308e4 Merge branch 'dev' into pr/106 2026-09-24 20:47:16 -05:00
Atony bfee7d44a2 Merge pull request #101 from All-Of-Us-Mods/cleanup
Clean project
2026-09-24 20:43:12 -05:00
Az 832d9fe7eb cleaning code is a sisyphean task 2026-09-24 20:27:19 -04:00
Az 458ca6e13d Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-24 20:18:43 -04:00
Atony f89ffb0d86 Find the latest role options collection at start
This should help avoid the api breaking from additional roles and additional settings.
2026-09-24 17:08:56 -05:00
David Higueros 0f8d5bc8ca Merge branch 'dev' into generic-option-list 2026-09-23 21:23:07 -06:00
Atony 2cfdbea51f Reduce duped code 2026-09-23 21:53:46 -05:00
AS 1c35e93121 Merge branch 'dev' into generic-option-list 2026-09-23 19:08:01 -04:00
Az 7107f66bec fix additional warning 2026-09-23 19:05:58 -04:00
AS 7589057bd9 Fix reference to advancedWikiTab in RoleGuidePatches 2026-09-23 19:00:56 -04:00
Az ca5a43a121 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-23 18:56:29 -04:00
Atony fc7eaef7c4 Add proper scroller implementation for tab 2026-09-23 17:53:50 -05:00
Az 8b897a17f4 add documentation 2026-09-23 18:17:15 -04:00
Atony a693cef676 Fix instant SubAssetHolder error 2026-09-23 16:37:03 -05:00
Atony e4a24d4164 Keep MiraHudHelper public 2026-09-23 16:32:51 -05:00
Az c7901baa34 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-23 14:58:17 -04:00
Az 8dee2d0bd0 Merge remote-tracking branch 'origin/xml-translations' into cleanup 2026-09-23 14:49:27 -04:00
Bohnked 0c3869ccf8 Translated using Weblate (Hebrew)
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/he/
2026-09-23 18:08:55 +00:00
walter 0ddfb9da63 Translated using Weblate (Hebrew)
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/he/
2026-09-23 18:08:55 +00:00
Turret 9cae38481c Translated using Weblate (Polish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pl/
2026-09-23 18:08:55 +00:00
Atony ef8b4af231 Fix masking and text positions 2026-09-23 01:57:10 -05:00
Atony a8cc2bc357 why did the languages get removed 2026-09-23 01:43:15 -05:00
Atony 173b0c37b3 Merge branch 'xml-translations' into dev 2026-09-23 01:41:29 -05:00
Atony b6881e4d77 Add WIP advanced role guide tab 2026-09-23 01:34:06 -05:00
Bohnked 76b8e8c1ee Translated using Weblate (Hebrew)
Currently translated at 36.8% (14 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/he/
2026-09-22 17:08:53 +00:00
David Higueros 1ab662bcf1 Adds a generic IModdedOptionList definition for easier typed iteration 2026-09-21 18:48:09 -06:00
Az 1f6558e7c3 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-21 18:35:15 -04:00
Sameer Umar 8fa87c0b7d Merge pull request #105 from TheDavSmasher/paginable-menu-locale
Custom Phone Menu fixes
2026-09-21 18:22:54 -04:00
David Higueros f2a4cf2b37 Added implicit operator to return Minigame component from Phone Menu 2026-09-21 15:18:48 -06:00
David Higueros 6dd36a80ab Changed paginable menu to use locale text for search and no results text 2026-09-21 15:11:29 -06:00
Bohnked c01d1ef7e9 Translated using Weblate (Hebrew)
Currently translated at 7.8% (3 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/he/
2026-09-21 15:08:53 +00:00
Az 208a388fc0 consistency 2026-09-21 00:26:00 -04:00
Az 957d6067a0 Update MiraLanguage.cs 2026-09-20 16:07:30 -04:00
Az e88eddfe56 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-20 15:58:10 -04:00
Atony fd354285bf Merge branch 'xml-translations' into dev 2026-09-20 09:51:42 -05:00
Atony 81da4a65b6 Merge branch 'xml-translations' of https://github.com/All-Of-Us-Mods/MiraAPI into xml-translations 2026-09-20 09:50:03 -05:00
Atony f10431f5b1 Add Arabic, Greek, Hebrew, and Lit-Chinese 2026-09-20 09:49:51 -05:00
Anthony a5de208f2e Deleted translation using Weblate (Hebrew) 2026-09-20 14:46:47 +00:00
walter 3edbe57aff Added translation using Weblate (Hebrew) 2026-09-20 13:30:27 +00:00
Az 2f56b46035 undo accidental deletion 2026-09-19 23:13:45 -04:00
Az 7c76219b17 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-19 23:12:52 -04:00
Atony 003d577a09 Create masked variants of tmp icons 2026-09-19 16:26:35 -05:00
Atony d41ab95fa4 Allow older mods to fallback med text to tab text 2026-09-19 15:16:09 -05:00
Atony 0cb0527ddf Fix null component errors for custom multi menu 2026-09-19 13:18:36 -05:00
Az 18452c0890 resolve new warnings 2026-09-18 12:51:30 -04:00
Az 0857ac5cf5 Merge remote-tracking branch 'origin/dev' into cleanup 2026-09-18 12:43:27 -04:00
Amber 9f0cf11ba1 Merge pull request #95 from TheDavSmasher/phone-menu-extension 2026-09-18 11:43:55 -04:00
Amber 82284c6540 Merge pull request #104 from soyFault/dev 2026-09-18 11:41:27 -04:00
Az d090a7c216 small corrections 2026-09-18 11:00:42 -04:00
Az 554a77fb64 merge ruleset into editorconfig 2026-09-13 18:40:37 -04:00
Az cd94666ed9 use aliases and add documentation for new method 2026-09-13 17:10:33 -04:00
Az 8b83dcbf1f re-add obsolete begin 2026-09-13 16:44:33 -04:00
Az 2c08e89e30 Revert "forgot shapeshifter's menu is a minigame itself"
This reverts commit f62513a3b4.
2026-09-13 16:41:19 -04:00
Fault 95d905c62b Merge branch 'localefault' into dev 2026-09-13 12:49:17 -06:00
Fault df4b32b462 Added localization changes for menus and Modifiers mini button in-game
Reused the Modifiers string
2026-09-13 12:42:52 -06:00
Leafly de349bfdea Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hant/
2026-09-13 17:08:53 +00:00
FangkuaiYa e5bb36fd50 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hans/
2026-09-12 14:08:55 +00:00
Az f62513a3b4 forgot shapeshifter's menu is a minigame itself 2026-09-12 01:27:16 -04:00
Az 34a4fcf69f change custom menu behaviour to wrap a script instead 2026-09-12 01:23:01 -04:00
Az 983b7be37a Merge branch 'dev' into pr/95 2026-09-12 00:20:02 -04:00
Az 1fbd93d6f7 Merge branch 'dev' into cleanup 2026-09-12 00:18:31 -04:00
Atony 16e3249b20 Expose ParseXmlFile 2026-09-10 00:02:10 -05:00
David Higueros 426cb22281 Merge branch 'dev' into phone-menu-extension 2026-09-09 19:52:43 -06:00
Atony 254de6e686 Fix language version 2026-09-09 10:55:06 -05:00
Draxen 5c7ebdac69 Translated using Weblate (Turkish)
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/tr/
2026-09-08 21:08:53 +00:00
AS db0a22866e Merge branch 'dev' into phone-menu-extension 2026-09-08 12:43:28 -04:00
Az 2ce04690a9 git is confusing 2026-09-07 15:33:11 -04:00
Az 875107e337 Merge branch 'dev' into cleanup 2026-09-07 15:32:46 -04:00
AS 8003afd9fb Merge pull request #92 from TheDavSmasher/util-extension-dev
Util Extension
2026-09-07 15:10:50 -04:00
Atony 85ed3b5d71 Merge branch 'dev' into util-extension-dev 2026-09-07 14:02:40 -05:00
Atony 7f79685696 fix other gamemode roles appearing in view pane 2026-09-06 21:06:28 -05:00
David Higueros 51044b1e89 Merge branch 'dev' into phone-menu-extension 2026-09-06 17:02:21 -06:00
David Higueros 6540f8b7bb Made EventHandler InvokeEvent extension methods 2026-09-06 16:40:21 -06:00
David Higueros caca3d92ad Merge branch 'dev' into util-extension-dev 2026-09-06 16:36:43 -06:00
Az 329cc85f96 make resharper happy 2026-09-06 09:54:01 -04:00
Az 8418b479d8 cleared all warnings
except for the todo ones, since they are supposed to be annoying
2026-09-06 07:03:31 -04:00
Az 7099e377ac Merge branch 'master' into cleanup 2026-09-04 15:06:03 -04:00
Leafly 9f39c10394 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 89.4% (34 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hant/
2026-09-04 03:29:46 +00:00
Leafly 27b5c49eb5 Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 89.4% (34 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hans/
2026-09-04 03:29:46 +00:00
Atony ef19f5f7de Merge pull request #103 from All-Of-Us-Mods/dev
0.5.0
2026-09-03 20:34:33 -05:00
Atony e619acbc2a Merge branch 'master' into dev 2026-09-03 20:30:53 -05:00
Atony 9e21726ced Fix MultiTargetButton being renamed? 2026-09-03 20:29:05 -05:00
Atony 7d7d6469ce Bump version 2026-09-03 02:29:00 -05:00
Atony 087ea2a4bd Clear up il2cpp warnings 2026-09-03 02:28:27 -05:00
Atony 81c6bac979 Add UiButtonResetEvent for better ui button grid 2026-09-02 15:16:32 -05:00
Leafly dcb6b14f5f Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 76.3% (29 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hant/
2026-09-02 19:29:46 +00:00
Leafly b48950909e Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 78.9% (30 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hans/
2026-09-02 19:29:46 +00:00
Faulo Fault 78066fae1c Translated using Weblate (Spanish)
Currently translated at 34.2% (13 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/es/
2026-09-02 19:29:46 +00:00
Atony 85fb507a43 Adjust gamemode role checks for info guide 2026-09-02 02:34:01 -05:00
Leafly 82e8764f54 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 44.7% (17 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/zh_Hant/
2026-09-01 16:29:46 +00:00
CraftR 9452b64887 Translated using Weblate (Polish)
Currently translated at 73.6% (28 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pl/
2026-09-01 16:29:46 +00:00
Olli 1a52c4f342 Translated using Weblate (German)
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/de/
2026-09-01 16:29:46 +00:00
Shade the Minior Fan fbc29dfb61 Translated using Weblate (Portuguese (Portugal))
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pt_PT/
2026-08-31 10:29:45 +00:00
Atony 3e2c6df829 Fix skip button vote area order 2026-08-30 19:00:57 -05:00
Atony 6c7b362f95 Add toggles to disable abilities upon use / vote 2026-08-30 18:24:35 -05:00
Atony 027d4763f3 Expose vote methods for other mods 2026-08-30 17:49:20 -05:00
Atony d2f6ba9718 Fix god-awful spacing in the roles guide 2026-08-29 22:55:44 -05:00
Atony 28871d0a01 Refresh role info on the info guide immediately 2026-08-29 22:35:47 -05:00
Atony b62b8ae216 Add more helpers for getting role strings 2026-08-29 22:35:10 -05:00
Atony 4bdddc6f8a Fix IdPart shenanigans 2026-08-29 19:39:21 -05:00
Atony fa47d1dec4 Merge branch 'xml-translations' of https://github.com/All-Of-Us-Mods/MiraAPI into xml-translations 2026-08-29 18:15:43 -05:00
Atony 004f62eb77 Tweak system for registering changes 2026-08-29 18:15:30 -05:00
Turret 3f7e30a5c3 Translated using Weblate (Polish)
Currently translated at 71.0% (27 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pl/
2026-08-29 21:20:07 +00:00
Faulo Fault 1d18a2ac7b Translated using Weblate (Spanish (Latin America))
Currently translated at 100.0% (38 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/es_419/
2026-08-29 21:20:07 +00:00
Shade the Minior Fan 663480c77f Translated using Weblate (Portuguese (Portugal))
Currently translated at 97.3% (37 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pt_PT/
2026-08-29 18:38:23 +00:00
Turret e95b52dbe8 Translated using Weblate (Polish)
Currently translated at 68.4% (26 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/pl/
2026-08-29 18:38:23 +00:00
Faulo Fault 76105b3116 Translated using Weblate (Spanish (Latin America))
Currently translated at 76.3% (29 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/es_419/
2026-08-29 18:38:23 +00:00
Anthony c82cc0e173 Translated using Weblate (Spanish)
Currently translated at 31.5% (12 of 38 strings)

Translation: Mira API/Mira API
Translate-URL: https://weblate.duikbo.at/projects/mira-api/mira-api/es/
2026-08-29 17:11:26 +00:00
Az ceab86cc19 Merge branch 'dev' into cleanup 2026-08-28 03:14:56 -04:00
Atony 2a432dd928 Add more localized strings 2026-08-28 00:37:14 -05:00
Atony 648e9ec185 Wrap up locale system 2026-08-27 23:51:25 -05:00
Atony 709b59300b Revert "Set role amount to 0 if chance is 0"
This reverts commit 794c762949.
2026-08-27 22:37:01 -05:00
Atony f064952aa6 Merge pull request #91 from HayashiUme/dev
Add XML Translation Api
2026-08-27 19:50:48 -05:00
Atony e532e558b6 Fix local tabs not being localized without hovering 2026-08-27 19:49:10 -05:00
Atony a3ed901a18 Translate local setting buttons 2026-08-27 19:36:33 -05:00
Atony 48aff163f9 Fix issues with local settings tab localization 2026-08-27 19:29:12 -05:00
Atony 28a77f96cb Fix capitalization of all xml strings 2026-08-27 18:21:14 -05:00
Atony 4206b74d12 Fix localization being applied 2026-08-26 00:24:02 -05:00
Atony 276ab0ac79 Fix overall localization 2026-08-25 19:02:55 -05:00
Atony 34148499b7 Fix up system registration for the most part 2026-08-25 18:26:54 -05:00
Atony 5b006ab7d3 Fix xml loading 2026-08-25 01:04:11 -05:00
Atony 47582897a8 Fix loading errors 2026-08-25 00:47:54 -05:00
Atony 80e1e6e454 Clear some warnings 2026-08-25 00:20:52 -05:00
Atony c449669cf2 Fix example plugin error 2026-08-25 00:15:39 -05:00
Atony 6da1048d09 Adjust system for Crowdin / Weblate support 2026-08-25 00:04:09 -05:00
Atony 6a7f58effa Add localization for gamemodes 2026-08-24 22:23:57 -05:00
Atony 39da0e7d08 Merge branch 'dev' into pr/91 2026-08-24 22:16:53 -05:00
Atony 5a23c38fba Merge pull request #102 from WanderingPix/meeting-abilities
Meeting abilities
2026-08-24 21:56:01 -05:00
Atony 0b1b223a6e Merge branch 'dev' into meeting-abilities 2026-08-24 21:48:54 -05:00
Atony a4a363b015 Merge branch 'dev-update' into meeting-abilities 2026-08-24 21:46:00 -05:00
Atony 794c762949 Set role amount to 0 if chance is 0 2026-08-24 16:18:39 -05:00
Atony e901abfc60 Merge branch 'dev' into dev-update 2026-08-24 15:49:12 -05:00
Atony 853a94681a Merge pull request #17 from All-Of-Us-Mods/game-modes
Game modes
2026-08-24 15:41:24 -05:00
wanderingpix d37222010d Document CenteredGridArrange 2026-08-22 13:37:02 +01:00
wanderingpix 1254de742a Fix text labels visible for disabled meeting abilties 2026-08-22 13:33:55 +01:00
wanderingpix 7bbbd7597d Refactor + untargeted meeting abilities 2026-08-22 13:32:14 +01:00
wanderingpix 4b7f17fa30 Fix skip button being broken 2026-08-21 19:30:24 +01:00
wanderingpix 06d62da24d Multi target meeting abilities 2026-08-21 17:00:24 +01:00
wanderingpix 0050c34225 Add singleton for accessing meeting buttons 2026-08-21 14:00:24 +01:00
wanderingpix 605c792e8f More documentation + Button outline color 2026-08-21 12:32:43 +01:00
wanderingpix 54d7c84e91 Edit example meeting ability 2026-08-21 11:52:48 +01:00
wanderingpix eb4bff1834 Document meeting abilities 2026-08-21 11:44:30 +01:00
wanderingpix e381de033e WIP Targeted meeting abilities API 2026-08-20 22:41:26 +01:00
Atony f230cff5e5 Add RoleMedDescription for the role guide 2026-08-19 23:28:00 -05:00
Atony 7c7d184d1f Wip faction display for roles
These need to be their own text object later.
2026-08-19 17:24:54 -05:00
Atony 6b923cfe01 Show nameplate on player list menu 2026-08-19 17:10:41 -05:00
Atony 247c765176 Fix bad ui because sloth sucx 2026-08-19 15:56:13 -05:00
Atony a3668b3150 Add proper support for modded roles in wiki 2026-08-19 13:52:33 -05:00
Atony aa4dd24ac3 Change options to V11 2026-08-19 13:31:36 -05:00
Atony cb0afe3095 Add ui resizing + wiki in the lobby 2026-08-19 13:29:03 -05:00
Atony 883222be82 Merge branch 'dev-update' into game-modes 2026-08-19 00:18:25 -05:00
Atony 6b774500dd Fix reimplemented Judge for good 2026-08-18 23:07:39 -05:00
Atony f96f75cea1 Fix issues with Judge and new meeting changes 2026-08-18 15:10:11 -05:00
linmeideli acfe345133 enhanced 2026-08-18 21:25:22 +08:00
David Higueros 503665ce2b Merge branch 'dev' into phone-menu-extension 2026-08-17 21:37:44 -06:00
David Higueros 8795aab37c Merge branch 'dev' into util-extension-dev 2026-08-17 21:24:29 -06:00
Atony 75662a6c25 Improve vent checks 2026-08-17 17:47:33 -05:00
Atony 73e4506016 Fix role tab issue 2026-08-17 17:41:24 -05:00
Atony a491592669 Allow gamemodes to be hidden when registered 2026-08-16 23:01:58 -05:00
Atony 01fef0f747 Fix vanilla advanced role settings tab breaking 2026-08-16 22:36:57 -05:00
Atony e22f4daf3e Fix the first role option being duped in the menu 2026-08-16 22:00:24 -05:00
Atony b83cb5cc29 Force role menu to be refreshed on gamemode change 2026-08-16 21:56:48 -05:00
Atony fb164a54e3 Fix broken game options tab for mods 2026-08-16 20:05:30 -05:00
Atony b6475161f1 Merge branch 'dev' into game-modes 2026-08-16 17:03:11 -05:00
Az 058a88e1d2 fix new warnings (thanks tony) 2026-08-16 04:29:17 -04:00
Az 907e4d4c34 Merge branch 'dev' into cleanup 2026-08-16 03:33:09 -04:00
Atony 411340d9e9 Fix role screenshots for combined spritesheets 2026-08-15 16:27:49 -05:00
Atony 57d2a6a9b5 Expose button event types 2026-08-15 16:00:05 -05:00
Atony 6f61a75bb7 Add BundleSubAssetHolder for packed spritesheets 2026-08-15 13:18:05 -05:00
AzandHayashiUmi 182a53442e Update README_zh.md
Co-Authored-By: HayashiUmi <2558527373@qq.com>
2026-08-14 06:57:19 -04:00
Az 35e4c7cc9d move chinese readme to translated folder 2026-08-14 02:18:15 -04:00
AS b2a5b236c0 Merge pull request #79 from TAIKgroup/master
Translate the README.md of MiraAPI into Simplified Chinese
2026-08-14 02:14:46 -04:00
linmeideli 525f987926 this check does not work for Unity's fake null 2026-08-14 13:38:28 +08:00
linmeideli 0b81a8808a Add Translation API 2026-08-14 13:05:30 +08:00
Az 33cafd1963 Merge branch 'dev' into cleanup 2026-08-13 23:56:53 -04:00
Az df07369376 clean up warnings and typos 2026-08-13 23:20:22 -04:00
David Higueros 695bbf8ea7 Registered menu types in Il2Cpp 2026-07-23 23:18:01 -06:00
Atony ca27188229 Merge branch 'dev' into pr/95 2026-07-23 19:27:45 -05:00
David Higueros 6ca0b25674 Fixed documentation typo lol 2026-07-18 18:02:17 -06:00
David Higueros 638db81335 Renamed parameters to match/improve documentation 2026-07-18 17:59:23 -06:00
David Higueros d603f5c953 Called SetNameplateAppearance on total selections equal case as well to account for confirming 2026-07-18 17:52:13 -06:00
David Higueros a776edc1f6 Added case for selecting a panel past the max selections 2026-07-18 17:51:13 -06:00
Atony 6b3ce1971d Merge branch 'dev' into phone-menu-extension 2026-07-18 08:50:34 -05:00
David Higueros 65e7770fc5 Merge branch 'dev' into util-extension-dev 2026-07-17 22:43:29 -06:00
David Higueros 8db3e10ce7 Added example for Visibility attribute 2026-07-17 22:41:23 -06:00
David Higueros e205a68330 Created examples of utilizing option lists 2026-07-17 22:37:41 -06:00
David Higueros ff6c6114cf Changed option list attributes to only accept strings with zero or one format parameters 2026-07-17 22:28:13 -06:00
David Higueros ff3478be54 Merge branch 'dev' into util-extension-dev 2026-07-17 19:24:35 -06:00
David Higueros 8246e58f00 Added base Begin for Paginable menu 2026-07-12 23:44:51 -06:00
David Higueros bae3ff8cea Added documentation to new classes and methods 2026-07-12 23:35:14 -06:00
David Higueros 24443c94ca Renamed parameter to match overload 2026-07-12 23:13:19 -06:00
David Higueros 7f023eb2c9 Created UiElement for confirm button 2026-07-12 22:55:03 -06:00
David Higueros 8700a2bf92 Added onSelection field to use earlier 2026-07-12 22:39:04 -06:00
David Higueros dc909037e8 Added shouldConfirm to single selection Begins with default false 2026-07-12 22:35:54 -06:00
David Higueros b3501b47c4 Added TODO comments for confirming multi-selection 2026-07-12 22:33:34 -06:00
David Higueros 9cfd8d6857 Clear selections on clicking back 2026-07-12 22:32:03 -06:00
David Higueros 54f594f947 Added 2 selections overload 2026-07-12 22:28:11 -06:00
David Higueros 4fb9897cc6 Made CustomPlayerMenu extend MultiSelectMenu 2026-07-12 22:19:59 -06:00
David Higueros 810facf50e Added canRepeat parameter 2026-07-12 22:12:56 -06:00
David Higueros e6621bd036 Added overload for single selections 2026-07-12 22:11:12 -06:00
David Higueros a5ec81b51f Created basic MultiSelect Custom Menu 2026-07-12 22:06:14 -06:00
David Higueros 88e17c4f58 Marked method as Obsolete to prevent it from being used. 2026-07-12 20:15:00 -06:00
David Higueros 49e5061d88 Created interfaces to define entries stored within the menu 2026-07-12 20:03:15 -06:00
David Higueros 6175628fdf Moved interface out of class 2026-07-12 19:54:58 -06:00
David Higueros d65203692b Created Paginable Menu subclass based on TOU GuesserMenu 2026-07-12 16:47:22 -06:00
David Higueros 04329e1c87 Added generic to only have certain types of entries 2026-07-12 16:22:55 -06:00
David Higueros 86912d75e4 Made menuEntries protected with initial value and BasicEntry sealed 2026-07-12 15:57:36 -06:00
David Higueros 1719bbf1b7 Added local variable, properties, and delegate type 2026-07-12 15:33:48 -06:00
David Higueros 59db03f39d Made Entry Selection check default to false 2026-07-12 15:10:13 -06:00
David Higueros 17db947003 Added base nameplate appearance method 2026-07-12 15:07:19 -06:00
David Higueros 48112c6a42 Added on mouse actions 2026-07-12 15:06:44 -06:00
David Higueros 1660dd72dc Renamed variables and removed variable 2026-07-12 14:48:43 -06:00
David Higueros 3f2a3286ed Added MenuDepth property 2026-07-12 13:38:23 -06:00
David Higueros cf374487be Merge branch 'dev' into phone-menu-extension 2026-07-12 12:44:42 -06:00
David Higueros 3f952da5b2 Added documentation and reverse implicit operator to BasicEntry 2026-07-12 12:42:42 -06:00
David Higueros f34697a5bf Moved option list attributes to subfolder 2026-07-10 19:02:06 -06:00
David Higueros 2ebb9f183c Allowed properties to be of type ModdedOption<bool> 2026-07-10 16:55:33 -06:00
David Higueros a2f363c8da Added instanceGet out parameter to provide the group instance when needed, null when static, or the Singleton Instance if another OptionGroup 2026-07-10 16:48:50 -06:00
David Higueros 121744e9ef Changed what flags are set for member finding 2026-07-10 16:13:14 -06:00
David Higueros bae3a245f1 Added case for properties, including list values and indexers 2026-07-10 15:34:38 -06:00
David Higueros 7b5e38b10e Added null member case 2026-07-10 14:10:33 -06:00
David Higueros 0895c58524 Added logic to determine method member validity and obtain visibility function 2026-07-10 14:01:17 -06:00
David Higueros 22d45c7c64 Applied attribute function in options manager 2026-07-10 13:28:50 -06:00
David Higueros 908ff4fa67 Created modded option visibility attribute 2026-07-10 13:26:52 -06:00
David Higueros e7184c9592 Added RoleID further util methods 2026-07-09 23:44:46 -06:00
David Higueros 98a0d3e2a6 Changed to use Register Panels 2026-07-09 16:27:33 -06:00
David Higueros 71234128d0 Added protected methods to add panels to custom Phone Menu 2026-07-09 16:27:31 -06:00
David Higueros 5d34f78fe0 Created parent class for phone menus 2026-07-09 16:27:27 -06:00
David Higueros 6598cb6933 Undid chaging property to init setter 2026-06-30 14:21:28 -06:00
David Higueros 706a836e4a Invert order of operation for ResetTarget so SetOutline(false) still has previous target 2026-06-30 14:17:29 -06:00
David Higueros 418889087a Merge branch 'dev' into util-extension-dev 2026-06-29 17:03:47 -06:00
David Higueros a4daa87bfc Added implicit operator to loadable asset which will call LoadAsset 2026-06-29 16:41:44 -06:00
David Higueros a8861f03ac Merge branch 'dev' into util-extension-dev 2026-06-23 15:19:17 -06:00
David Higueros 25794b149b Created attribute versions for option list 2026-06-21 22:05:14 -06:00
David Higueros c3384ef850 Changed parent attribute method signatures 2026-06-21 21:43:26 -06:00
David Higueros 3fd3c68aaf Removed propertyinfo field 2026-06-21 21:06:14 -06:00
David Higueros edeec4ca4d Implemented list value patches 2026-06-21 21:01:48 -06:00
David Higueros 67727945da Changed to store a reference to the property's value instance 2026-06-21 20:42:10 -06:00
David Higueros 68fb1d2b58 Added patches to the list's getter and setter 2026-06-21 20:27:46 -06:00
David Higueros 80b8af85cb Added IList cast and null check 2026-06-21 19:59:49 -06:00
David Higueros e5658df8c5 Created parent attribute class to share methods 2026-06-21 19:35:14 -06:00
David Higueros 887be871f0 Changed abstract methods to handle casting and calling abstract overload 2026-06-21 19:28:17 -06:00
David Higueros 11fa9581fa Added abstract option list attribute 2026-06-21 18:54:57 -06:00
David Higueros 604d2f375d Added interface to error logger check 2026-06-21 13:54:08 -06:00
David Higueros 39a43f5395 Removed unnecessary private get 2026-06-21 12:57:16 -06:00
David Higueros b5e35e2533 Added constructor overload 2026-06-21 12:38:38 -06:00
David Higueros bea37d815a Applied Modded option list into options manager 2026-06-20 02:06:25 -06:00
David Higueros e115317ad4 Removed generic from interface for easier manager registering alongside property 2026-06-20 01:56:14 -06:00
David Higueros 6a899ba27d Removed properties that aren't required 2026-06-20 01:29:56 -06:00
David Higueros ad6ca6fa3e Simplified property setters and getter 2026-06-20 01:27:01 -06:00
David Higueros 93d5ebf15a Removed Preset fields from interface that won't be used 2026-06-20 01:20:08 -06:00
David Higueros 007c202058 Changed properties to init instead of set to allow using object initializer but not changing the values later 2026-06-20 00:52:07 -06:00
David Higueros f9f7b7e667 Expanded setters to set the inner options values as well 2026-06-19 23:30:58 -06:00
David Higueros beb7443c9a Extended from IReadOnlyList for potential easier iteration 2026-06-19 23:30:18 -06:00
David Higueros be1d77bc6c Removed readonly properties that can be more easily replicated with the indexer 2026-06-19 23:13:10 -06:00
David Higueros b0be07192e Removed middleman classes and fields that can be handled much easier 2026-06-19 23:12:08 -06:00
David Higueros f9a33ce109 Created concrete class for nested IModdedOptions that aren't the concrete class 2026-06-19 22:32:16 -06:00
David Higueros 71a3461432 Created subclass with generic values 2026-06-19 22:23:35 -06:00
David Higueros 612597566b Created abstract class for Option list 2026-06-19 21:35:03 -06:00
David Higueros 42eba6280d Created interface for sets of equal/comparable modded options 2026-06-19 21:05:23 -06:00
David Higueros 690b1b2dae Added generic Enum Option attribute 2026-06-19 14:23:27 -06:00
David Higueros 01fe88ede9 Added equivalent extensions of Modifier OfType methods 2026-06-19 13:47:07 -06:00
David Higueros 34c172b6f1 Added methods to get modifiers from an interface type 2026-06-19 13:40:15 -06:00
David Higueros 29779f3711 Added similar extensions with new try remove methods 2026-06-19 13:24:28 -06:00
David Higueros 388b6ec676 Added TryRemoveModifier methods to simplify get and remove modifiers 2026-06-19 13:17:31 -06:00
David Higueros d64eaddb0e Added remove self method for modifiers 2026-06-19 12:43:28 -06:00
wangzeyu b100f92525 Update README_zh.md 2026-01-24 06:34:59 +08:00
wangzeyu 202b098857 Create README_zh.md 2026-01-24 06:33:49 +08:00
wangzeyu fedba71889 Update README.md 2026-01-23 05:22:50 +08:00
394 changed files with 17153 additions and 5075 deletions

No files matched your search

+106 -1
View File
@@ -1 +1,106 @@
[*.{cs,vb}]
[*.{cs,vb}]
# IDE0008: Use explicit type
dotnet_diagnostic.IDE0008.severity = none
# IDE0160: Convert to block scoped namespace
dotnet_diagnostic.IDE0160.severity = none
# IDE0011: Add braces
dotnet_diagnostic.IDE0011.severity = none
# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
dotnet_diagnostic.CS8618.severity = none
# CA1805: Do not initialize unnecessarily
dotnet_diagnostic.CA1805.severity = none
# S3358: Ternary operators should not be nested
dotnet_diagnostic.S3358.severity = none
# IDE1006: Naming Styles
dotnet_diagnostic.IDE1006.severity = none
# S3267: Use LINQ methods to simply foreach loops
dotnet_diagnostic.S3267.severity = none
# S125: Sections of code should not be commented out
dotnet_diagnostic.S125.severity = none
# SA1512: Single-line comments should not be followed by blank line
dotnet_diagnostic.SA1512.severity = none
# S2696: Instance members should not write to "static" fields
dotnet_diagnostic.S2696.severity = none
# S2245: Pseudorandom number generators (PRNGs) should not be used in security contexts
dotnet_diagnostic.S2245.severity = none
# IDE0130: Namespace does not match folder structure
dotnet_diagnostic.IDE0130.severity = none
# SA1633: File should have header
dotnet_diagnostic.SA1633.severity = none
# SA1101: Prefix local calls with this
dotnet_diagnostic.SA1101.severity = none
# SA1600: Elements should be documented
dotnet_diagnostic.SA1600.severity = none
# SA1516: Elements should be separated by blank line
dotnet_diagnostic.SA1516.severity = none
# SA1503: Braces should not be omitted
dotnet_diagnostic.SA1503.severity = none
# SA1201: Elements should appear in the correct order
dotnet_diagnostic.SA1201.severity = none
# SA1402: File may only contain a single type
dotnet_diagnostic.SA1402.severity = none
# SA1204: Static elements should appear before instance elements
dotnet_diagnostic.SA1204.severity = none
# SA1202: Elements should be ordered by access
dotnet_diagnostic.SA1202.severity = none
# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
# SA1313: Parameter names should begin with lower-case letter
dotnet_diagnostic.SA1313.severity = none
# SA1636: The file header copyright text should match the copyright text from the settings
dotnet_diagnostic.SA1636.severity = none
# SA1309: Field names should not begin with underscore
dotnet_diagnostic.SA1309.severity = none
# SA1407: Arithmetic expressions should declare precedence
dotnet_diagnostic.SA1407.severity = none
# SA1133: Each attribute should be placed in its own set of square brackets
dotnet_diagnostic.SA1133.severity = none
# IDE0001: Simplify Names
dotnet_diagnostic.IDE0001.severity = warning
# IDE0079: Remove unnecessary suppression
dotnet_diagnostic.IDE0079.severity = none
# S3903: Types should be defined in named namespaces
dotnet_diagnostic.S3903.severity = none
# IDE0045: Convert to conditional expression
dotnet_diagnostic.IDE0045.severity = none
# IDE0046: Convert to conditional expression
dotnet_diagnostic.IDE0046.severity = none
# S1244: Floating point numbers should not be tested for equality
dotnet_diagnostic.S1244.severity = none
# S2325: Methods and properties that don't access instance data should be static
dotnet_diagnostic.S2325.severity = none
+2 -2
View File
@@ -1,9 +1,9 @@
.git/
.idea/
.vs/
.vs*/
obj/
bin/
.qodo/
*.user
*.lock.json
*.lock.json
+1 -1
View File
@@ -2,7 +2,7 @@
<ItemGroup>
<PackageReference Include="Reactor" Version="2.5.0-ci.371" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2026.6.5" PrivateAssets="all" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2026.9.29" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
+7 -10
View File
@@ -8,7 +8,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.4.3</VersionPrefix>
<VersionPrefix>0.5.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
@@ -28,18 +28,15 @@
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>recommended</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<NoWarn>$(NoWarn);SA0001</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Roslynator.Analyzers" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="5.0.0" PrivateAssets="all" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.32.0.713" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\stylecop.json" Link="stylecop.json" Visible="false" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/stylecop.json" Link="stylecop.json" Visible="false" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
+29
View File
@@ -0,0 +1,29 @@
<assembly name="0Harmony">
<member name="T:HarmonyLib.HarmonyPatch">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor(JetBrains.Annotations.ImplicitUseTargetFlags)">
<argument>3</argument>
</attribute>
</member>
<member name="T:HarmonyLib.HarmonyPrefix">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyPostfix">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyTranspiler">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyFinalizer">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyPrepare">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyTargetMethod">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
<member name="T:HarmonyLib.HarmonyTargetMethods">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor" />
</member>
</assembly>
+7
View File
@@ -0,0 +1,7 @@
[*.{cs,vb}]
# SA1602: Enumeration items should be documented.
dotnet_diagnostic.SA1602.severity = none
# SA1601: Partial elements should be documented
dotnet_diagnostic.SA1601.severity = none
@@ -13,7 +13,7 @@ namespace MiraAPI.Example.Buttons.Freezer;
public class FreezeButton : CustomActionButton<PlayerControl>
{
public override string Name => "Freeze";
public override string Name => "button.freeze";
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
public override bool PauseTimerInVent => true;
+4 -6
View File
@@ -9,10 +9,8 @@ namespace MiraAPI.Example.Buttons;
public class MeetingButton : CustomActionButton
{
public override string Name => "Call Meeting";
public override string Name => "button.callMeeting";
public override float Cooldown => 15;
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
@@ -30,10 +28,10 @@ public class MeetingButton : CustomActionButton
PlayerControl.LocalPlayer.NetTransform.Halt();
var minigame = Object.Instantiate(bt.MinigamePrefab, Camera.main!.transform, false);
var taskAdderGame = minigame as TaskAdderGame;
if (taskAdderGame != null)
var taskAdderGame = minigame.TryCast<TaskAdderGame>();
if (taskAdderGame)
{
taskAdderGame.SafePositionWorld = bt.SafePositionLocal + (Vector2)bt.transform.position;
taskAdderGame!.SafePositionWorld = bt.SafePositionLocal + (Vector2)bt.transform.position;
}
minigame.transform.localPosition = new Vector3(0f, 0f, -50f);
@@ -10,7 +10,7 @@ namespace MiraAPI.Example.Buttons;
public class NeutralKillerButton : CustomActionButton
{
public override string Name => "Win Game";
public override string Name => "button.winGame";
public override float Cooldown => 0f;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind;
@@ -12,7 +12,7 @@ namespace MiraAPI.Example.Buttons.Teleporter;
public class TeleportButton : CustomActionButton
{
public override string Name => "Teleport";
public override string Name => "button.teleport";
public override float Cooldown => OptionGroupSingleton<TeleporterOptions>.Instance.TeleportCooldown.Value;
@@ -61,11 +61,14 @@ public class TeleportButton : CustomActionButton
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
foreach (var cam in Camera.allCameras)
cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = zoomDistance;
foreach (var cam in Camera.allCameras)
cam.orthographicSize = zoomDistance;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
}
@@ -75,12 +78,14 @@ public class TeleportButton : CustomActionButton
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
foreach (var cam in Camera.allCameras)
cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = 3f;
foreach (var cam in Camera.allCameras)
cam.orthographicSize = 3f;
HudManager.Instance.ShadowQuad.gameObject.SetActive(true);
IsZoom = false;
+7 -4
View File
@@ -1,4 +1,6 @@
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Translation;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -6,21 +8,22 @@ namespace MiraAPI.Example.Buttons;
public class ThinkButton : CustomActionButton
{
public override string Name => "Think";
public override string Name => "button.think";
public override float Cooldown => 15f;
public override float EffectDuration => 10f;
public override int MaxUses => 1;
public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind Keybind => MiraGlobalKeybinds.SecondaryAbility;
protected override void OnClick()
{
// This button does absolutely nothing besides changing its text
Button!.OverrideText("Thinking...");
Button!.OverrideText("button.think.thinking".Translate());
}
public override void OnEffectEnd()
{
Button!.OverrideText("Think");
Button!.OverrideText("button.think".Translate());
}
public override bool IsEffectCancellable()
+1
View File
@@ -9,5 +9,6 @@ public static class ExampleAssets
// Credit to EpicHorrors for the teleport button asset.
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
public static LoadableResourceAsset SharpshootButton { get; } = new("MiraAPI.Example.Resources.SharpshootButton.png");
public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg");
}
+10 -3
View File
@@ -1,4 +1,6 @@
using MiraAPI.Colors;
using MiraAPI.Colors.ColorSpaces;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example;
@@ -6,12 +8,17 @@ namespace MiraAPI.Example;
[RegisterCustomColors]
public static class ExampleColors
{
public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f))
public static CustomColor Cerulean { get; } = new("color.cerulean", new Color(0.0f, 0.48f, 0.65f))
{
ColorBrightness = CustomColorBrightness.Lighter,
};
public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
public static CustomColor Rose { get; } = new("color.rose", new Color(0.98f, 0.26f, 0.62f));
public static CustomColor Gold { get; } = new("Gold", new Color(1.0f, 0.84f, 0.0f));
public static CustomColor Gold { get; } = new("color.gold", new Color(1.0f, 0.84f, 0.0f));
public static VariableColor Rainbow { get; } = new("color.rainbow", t => new HsbColor(MathUtilities.PingPong(t, 1f, 0.3f), 1f, 1f))
{
ColorBrightness = CustomColorBrightness.Lighter,
};
}
+2 -2
View File
@@ -1,4 +1,4 @@
using MiraAPI.Keybinds;
using MiraAPI.Keybinds;
using Rewired;
namespace MiraAPI.Example;
@@ -6,5 +6,5 @@ namespace MiraAPI.Example;
[RegisterCustomKeybinds]
public static class ExampleKeybinds
{
public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, [ModifierKey.Shift]);
public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, ControllerButton.RightStick, [ModifierKey.Shift]);
}
+3 -3
View File
@@ -8,7 +8,7 @@ namespace MiraAPI.Example;
public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config)
{
public override string TabName => "Mira Example";
public override string TabName => "settings.miraExample";
protected override bool ShouldCreateLabels => false;
public override LocalSettingTabAppearance TabAppearance => new()
@@ -18,7 +18,7 @@ public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config)
};
[LocalSettingsButton]
public LocalSettingsButton ExampleButton { get; private set; } = new("Example Button", OnExampleButtonClick);
public LocalSettingsButton ExampleButton { get; private set; } = new("settings.miraExample.button.exampleButton", OnExampleButtonClick);
[LocalToggleSetting]
public ConfigEntry<bool> ExampleToggle { get; private set; } = config.Bind("General", "Example Bool", true);
@@ -33,7 +33,7 @@ public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config)
public ConfigEntry<float> ExampleNumber { get; private set; } = config.Bind("General", "Example Number", 4f);
[LocalNumberSetting(min: 0.1f, max: 2.5f, increment: 0.1f, formatString: "0.0", suffixType: MiraNumberSuffixes.Multiplier)]
public ConfigEntry<float> ExampleFloatNumber { get; private set; } = config.Bind("General", "Example Multplier", 1f);
public ConfigEntry<float> ExampleFloatNumber { get; private set; } = config.Bind("General", "Example Multiplier", 1f);
[LocalEnumSetting]
public ConfigEntry<ExampleEnumSetting> ExampleEnum { get; private set; } = config.Bind("General", "Example Enum", ExampleEnumSetting.Fries);
+14 -5
View File
@@ -3,11 +3,10 @@ using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using MiraAPI.Translation;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using UnityEngine;
namespace MiraAPI.Example;
@@ -18,10 +17,20 @@ namespace MiraAPI.Example;
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class ExamplePlugin : BasePlugin, IMiraPlugin
{
public ExamplePlugin()
{
MiraLocaleManager.Register("mira.example");
}
public Harmony Harmony { get; } = new(Id);
public string OptionsTitleText => "Mira API\nExample Mod";
public string CustomOptionMenuNameTwo => "angxl's Options";
public ConfigFile GetConfigFile() => Config;
public string OptionsTitleText { get; } = "Mira API\nExample Mod";
public string CustomOptionMenuNameTwo { get; } = "Example Options";
public ConfigFile GetConfigFile()
{
return Config;
}
public override void Load()
{
ExampleEventHandlers.Initialize();
@@ -1,5 +1,6 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using UnityEngine;
@@ -14,7 +15,7 @@ public class NeutralKillerGameOver : CustomGameOver
public override void AfterEndGameSetup(EndGameManager endGameManager)
{
endGameManager.WinText.text = "Outcast Killer Wins!";
endGameManager.WinText.text = "gameover.outcastKiller.win".Translate();
endGameManager.WinText.color = Color.magenta;
endGameManager.BackgroundBar.material.SetColor(ShaderID.Color, Color.magenta);
}
@@ -0,0 +1,29 @@
using MiraAPI.Example.Roles;
using MiraAPI.MeetingAbilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.MeetingAbilities;
public class EndMeetingButton : MeetingActionButton
{
public override string Name => "End Meeting";
public override float Cooldown => 15;
public override float InitialCooldown => 30;
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override bool Enabled(RoleBehaviour? role)
{
return PlayerControl.LocalPlayer != null && role is MayorRole;
}
protected override void OnClick()
{
MeetingHud.Instance.RpcClose();
}
}
@@ -0,0 +1,37 @@
using MiraAPI.MeetingAbilities;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.MeetingAbilities;
public class ShootButton : TargetedMeetingButton
{
public override string Name => "Shoot";
public override int MaxUses => 3;
public override float InitialCooldown => 5;
public override float Cooldown => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.SharpshootButton;
public override Color OutlineColor => Color.red;
public override bool Enabled(RoleBehaviour r)
{
return r.IsImpostor;
}
protected override void OnClick(PlayerVoteArea playerVoteArea)
{
playerVoteArea.SetDisabled();
var player = playerVoteArea.GetPlayer();
if (player != null)
{
PlayerControl.LocalPlayer.RpcCustomMurder(player);
}
}
}
@@ -0,0 +1,54 @@
using MiraAPI.MeetingAbilities;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.MeetingAbilities;
public class ShotgunButton : MultiTargetMeetingButton
{
public override string Name => "Shotgun";
public override int MaxUses => 3;
public override float InitialCooldown => 5;
public override float Cooldown => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.SharpshootButton;
public override Color OutlineColor => Color.red;
public override bool Enabled(RoleBehaviour r)
{
return r.IsImpostor;
}
protected override void OnClick(PlayerVoteArea playerVoteArea)
{
// Emptu because it only triggers the actual kills once all meeting abilities have been used.
}
public override LoadableAsset<Sprite> SpriteActive => ExampleAssets.CallMeetingButton;
public override int TargetsCount => 4;
protected override void OnSelect(PlayerVoteArea playerVoteArea, bool toggle)
{
HudManager.Instance.PlayerCam.ShakeScreen(0.5f, 2);
}
public override void OnFinish()
{
foreach (var playerVoteArea in Targets.Keys)
{
playerVoteArea.SetDisabled();
var player = playerVoteArea.GetPlayer();
if (player != null)
{
PlayerControl.LocalPlayer.RpcCustomMurder(player);
}
}
}
}
+7 -6
View File
@@ -1,17 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<Description>Example mod for Mira API</Description>
<Authors>Angxl, XtraCube</Authors>
<Authors>Author Name</Authors>
<NoWarn>$(NoWarn);EnableGenerateDocumentationFile</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" /> <!-- You typically would use the nuget package here: AllOfUs.MiraAPI -->
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<Import Project="..\AmongUs.props" />
</Project>
+3 -2
View File
@@ -2,6 +2,7 @@
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Translation;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -9,7 +10,7 @@ namespace MiraAPI.Example.Modifiers;
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
public override string ModifierName => MiraLocaleManager.Get("modifier.captain");
public override LoadableAsset<Sprite>? ModifierIcon => ExampleAssets.CallMeetingButton;
public override void OnDeath(DeathReason reason)
@@ -19,7 +20,7 @@ public class CaptainModifier : GameModifier
public override string GetDescription()
{
return $"You can call a meeting from anywhere on the map.";
return MiraLocaleManager.Get("modifier.captain.TabDescription");
}
public override int GetAssignmentChance()
@@ -1,13 +1,13 @@
using Il2CppSystem;
using MiraAPI.Example.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.Modifiers.Types;
using MiraAPI.Translation;
using UnityEngine;
namespace MiraAPI.Example.Modifiers.Freezer;
public class FreezeModifier : TimedModifier
{
public override string ModifierName => "Freezed";
public override string ModifierName => MiraLocaleManager.Get("modifier.freezed");
public override bool HideOnUi => false;
public override float Duration => 15f;
public override bool AutoStart => true;
@@ -27,7 +27,7 @@ public class FreezeModifier : TimedModifier
if (Player.AmOwner || PlayerControl.LocalPlayer.Data.Role is FreezerRole)
{
Player.cosmetics.SetOutline(true, new Nullable<Color>(Palette.LightBlue));
Player.cosmetics.SetOutline(true, new Il2CppSystem.Nullable<Color>(Palette.LightBlue));
}
}
@@ -1,14 +1,15 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Translation;
namespace MiraAPI.Example.Modifiers;
public class HighPriorityModifier : GameModifier
{
public override string ModifierName => "High Priority";
public override string ModifierName => MiraLocaleManager.Get("modifier.highPriority");
public override string GetDescription()
{
return "You are high priority! Idrk what this means either tbh...";
return MiraLocaleManager.Get("modifier.highPriority.TabDescription");
}
public override int GetAssignmentChance()
+66 -9
View File
@@ -1,31 +1,88 @@
using MiraAPI.GameOptions;
using System.Diagnostics.CodeAnalysis;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Options;
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Demo.")]
[SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Read above.")]
[SuppressMessage("Minor Code Smell", "S3400:Methods should not return constants", Justification = "Read above.")]
public class ExampleOptions : AbstractOptionGroup
{
public override string GroupName => "Example Options 1";
public override string GroupName => "options.exampleOptions1";
public override Color GroupColor => Color.green;
public override bool ShowInMatchInfoGuide => true;
[ModdedToggleOption("Toggle Opt 1")]
public bool ToggleOpt { get; set; } = false;
[ModdedToggleOption("options.exampleOptions1.toggleOpt1")]
[ModdedOptionVisibility] // Automatically resolves to using ToggleOpt1Visibility, can also target toggle options
public bool ToggleOpt1 { get; set; } = false;
[ModdedToggleOption("Toggle Opt 2")]
[ModdedToggleOption("options.exampleOptions1.toggleOpt2")]
[ModdedOptionVisibility(nameof(ToggleOpt1))] // ToggleOpt2 will be visible only when ToggleOpt1 is true
public bool ToggleOpt2 { get; set; } = true;
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
[ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
[ModdedOptionVisibility(nameof(NumberOptVisibility))] // Can also target static methods
public float NumberOpt { get; set; } = 4f;
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
public BestApi Opt { get; set; } = BestApi.MiraAPI;
[ModdedNumberOption("options.exampleOptions1.numberOpt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Distance)]
public int NumberOpt2 { get; set; } = 4; // Can also be int!
[ModdedEnumOption("options.exampleOptions1.bestApi", typeof(Api), ["Mira API", "Mitochondria", "Reactor"])]
public Api BestApi { get; set; } = Api.MiraAPI;
[ModdedEnumOption<Api>("options.exampleOptions1.worstApi", ["Mira API", "Mitochondria", "Reactor"])]
// These three can target both instance and static methods
[ModdedOptionGetter(nameof(GetApi))] // Can also automatically resolve to {Option Name}Getter
[ModdedOptionSetter(nameof(SetApi))] // Can also automatically resolve to {Option Name}Setter
[ModdedOptionChanged(nameof(ApiChanged))] // Can also automatically resolve to {Option Name}Changed
public Api WorstApi { get; set; } = Api.MiraAPI;
[ModdedEnumOptionList<ValueState>("Value State {0}", ["None", "Primary", "Secondary"])]
public ModdedArray<ValueState> ValueStates { get; set; } = [ValueState.Primary, ValueState.Secondary, ValueState.None, ValueState.None, ValueState.None];
[ModdedPlayerOption("options.exampleOptions1.players")]
public NetworkedPlayerInfo PlayerSelected { get; set; }
public bool ToggleOpt1Visibility()
{
return true;
}
public static bool NumberOptVisibility()
{
return true;
}
public static Api GetApi(Api currentValue)
{
return currentValue;
}
public static Api SetApi(Api newValue, Api oldValue)
{
return default;
}
public static void ApiChanged(Api currentValue)
{
// Something
}
}
public enum BestApi
public enum Api
{
MiraAPI,
Mitochondria,
Reactor,
}
public enum ValueState
{
None,
Primary,
Secondary,
}
+12 -8
View File
@@ -6,24 +6,28 @@ namespace MiraAPI.Example.Options;
public class ExampleOptions2 : AbstractOptionGroup
{
public override string GroupName => "Example Options 2";
public override string GroupName => "options.exampleOptions2";
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
public override MenuCategory ParentMenu => MenuCategory.CustomTwo;
public override bool ShowInMatchInfoGuide => true;
public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false);
public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever");
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
public ModdedPlayerOption PlayerOption2 { get; } = new("options.exampleOptions2.youMustChoseAPlayer", false);
public ModdedPlayerOption PlayerOption { get; } = new("options.exampleOptions2.ehWhatever");
public ModdedToggleOption ToggleOpt1 { get; } = new("options.exampleOptions2.toggleOption1", false);
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
public ModdedOptionList<ModdedNumberOption> PlayerNums { get; } = new(5, i =>
new($"PlayerNum{i}", 2, 0, 5, 1, Utilities.MiraNumberSuffixes.None));
public ModdedToggleOption ToggleOpt2 { get; } = new("options.exampleOptions2.toggleOption2", false)
{
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1, // implicit cast from ModdedToggleOption to bool
};
public ModdedEnumOption<TestingData> EnumOpt { get; } = new("Enum Opt", 0)
public ModdedEnumOption<TestingData> EnumOpt { get; } = new("options.exampleOptions2.enumOpt", 0)
{
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
Getter = currentValue => currentValue,
Setter = (newValue, oldValue) => newValue,
};
}
@@ -6,14 +6,14 @@ namespace MiraAPI.Example.Options.Modifiers;
public class CaptainModifierSettings : AbstractOptionGroup<CaptainModifier>
{
public override string GroupName => "Captain Settings";
public override string GroupName => "options.captainSettings";
[ModdedNumberOption("Amount", 0, 5)]
[ModdedNumberOption("options.captainSettings.amount", 0, 5)]
public float Amount { get; set; } = 1;
[ModdedNumberOption("Chance", 0, 100, 10)]
[ModdedNumberOption("options.captainSettings.chance", 0, 100, 10)]
public float Chance { get; set; } = 50;
[ModdedNumberOption("Uses", 1, 5)]
[ModdedNumberOption("options.captainSettings.uses", 1, 5)]
public float NumUses { get; set; } = 3;
}
@@ -6,8 +6,8 @@ namespace MiraAPI.Example.Options.Modifiers;
public class FreezeModifierSettings : AbstractOptionGroup<FreezeModifier>
{
public override string GroupName => "Freeze";
public override string GroupName => "options.freezeModifierSettings";
[ModdedToggleOption("Use Color")]
[ModdedToggleOption("options.freezeModifierSettings.useColor")]
public bool UseColor { get; set; } = true;
}
@@ -5,15 +5,13 @@ namespace MiraAPI.Example.Options.Modifiers;
public class GeneralModifierOptions : AbstractOptionGroup
{
public override string GroupName => "General";
public override string GroupName => "options.generalModifier";
public override uint GroupPriority => 0;
public override MenuCategory ParentMenu => MenuCategory.Modifiers;
[ModdedToggleOption("Some Boolean Option")]
[ModdedToggleOption("options.generalModifier.someBooleanOption")]
public bool SomeBooleanOption { get; set; } = true;
[ModdedNumberOption("Number Option", 0, 10, 1)]
[ModdedNumberOption("options.generalModifier.numberOption", 0, 10)]
public float NumberOption { get; set; } = 5;
}
@@ -13,11 +13,11 @@ public class FreezerRoleSettings : AbstractOptionGroup<FreezerRole>
TmpSpriteUtils.CreateSpriteAsset(
MiraAssets.ImpostorFile.LoadAsset(),
"ApiExample.Role.Impostor.FreezerRole"));
public override string GroupName => "Custom Role";
public override string GroupName => "ApiExample.Role.Freezer";
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
[ModdedNumberOption("ApiExample.Role.Freezer.Settings.FreezeDuration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
public float FreezeDuration { get; set; } = 5;
[ModdedNumberOption("Freeze Uses", 1, 5)]
[ModdedNumberOption("ApiExample.Role.Freezer.Settings.FreezeUses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
@@ -8,13 +8,13 @@ namespace MiraAPI.Example.Options.Roles;
public class TeleporterOptions : AbstractRoleOptionGroup<TeleporterRole>
{
public override string GroupName => "Teleporter";
public override string GroupName => "ApiExample.Role.Teleporter";
public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds);
public ModdedNumberOption TeleportCooldown { get; set; } = new("ApiExample.Role.Teleporter.Settings.TeleportCooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds);
[ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)]
[ModdedNumberOption("ApiExample.Role.Teleporter.Settings.TeleportDuration", 5, 25, 1, MiraNumberSuffixes.Seconds)]
public float TeleportDuration { get; set; } = 10;
[ModdedNumberOption("Zoom Distance", 4, 15)]
[ModdedNumberOption("ApiExample.Role.Teleporter.Settings.ZoomDistance", 4, 15)]
public float ZoomDistance { get; set; } = 6;
}
@@ -0,0 +1,86 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="ApiExample.Role.Crewmate.Teleporter">Teleporter</string>
<string name="ApiExample.Role.Crewmate.Teleporter.IntroBlurb">Zoom out and teleport across the map!</string>
<string name="ApiExample.Role.Crewmate.Teleporter.MedDescription">Zoom out and teleport across the map!</string>
<string name="ApiExample.Role.Crewmate.Teleporter.TabDescription">Zoom out and teleport across the map!</string>
<string name="ApiExample.Role.Crewmate.Teleporter.WikiDescription">The Teleporter is a Crewmate role that can move around the map using their dope-ass ability.</string>
<string name="ApiExample.Role.Impostor.Freezer">Freezer</string>
<string name="ApiExample.Role.Impostor.Freezer.IntroBlurb">Get iced, nerd.</string>
<string name="ApiExample.Role.Impostor.Freezer.MedDescription">Freeze others for a duration of time.</string>
<string name="ApiExample.Role.Impostor.Freezer.TabDescription">Freeze another player for a duration of time.</string>
<string name="ApiExample.Role.Impostor.Freezer.WikiDescription">The Freezer is an Impostor role that can freeze players. Or something along those lines.</string>
<string name="ApiExample.Role.Crewmate.Mayor">Mayor</string>
<string name="ApiExample.Role.Crewmate.Mayor.IntroBlurb">Democracy is the best policy.</string>
<string name="ApiExample.Role.Crewmate.Mayor.MedDescription">Vote out the Impostors with more votes.
Your votes have double the power.</string>
<string name="ApiExample.Role.Crewmate.Mayor.TabDescription">You get one extra vote in the meeting!</string>
<string name="ApiExample.Role.Crewmate.Mayor.WikiDescription">The Mayor is a Crewmate role that gets two votes in the meeting, instead of the singular vote other Crewmates get.</string>
<string name="ApiExample.Role.Crewmate.Chameleon">Chameleon</string>
<string name="ApiExample.Role.Crewmate.Chameleon.IntroBlurb">Become one with the environment.</string>
<string name="ApiExample.Role.Crewmate.Chameleon.MedDescription">Fade out of everyone's vision by not moving.</string>
<string name="ApiExample.Role.Crewmate.Chameleon.TabDescription">Stay invisible while not moving.</string>
<string name="ApiExample.Role.Crewmate.Chameleon.WikiDescription">The Chameleon is a Crewmate role that slowly becomes invisible while standing still.</string>
<string name="ApiExample.Role.Outcast.OutcastKiller">Outcast Killer</string>
<string name="ApiExample.Role.Outcast.OutcastKiller.IntroBlurb">Kill everyone else.</string>
<string name="ApiExample.Role.Outcast.OutcastKiller.MedDescription">Be the last outcast standing.
Eliminate every other faction.</string>
<string name="ApiExample.Role.Outcast.OutcastKiller.TabDescription">Outcast who can kill.</string>
<string name="ApiExample.Role.Outcast.OutcastKiller.WikiDescription">The Outcast Killer is an Outcast role that can kill, like some sort of neutral killer...</string>
<string name="outcast">Outcast</string>
<string name="outcast.group">Outcast Roles</string>
<string name="outcast.roleTitle">OUTCAST</string>
<string name="outcast.teamIntroDescription">You are an Outcast. You do not have a team.</string>
<string name="button.think">Think</string>
<string name="button.think.thinking">Thinking...</string>
<string name="button.freeze">Freeze</string>
<string name="button.teleport">Teleport</string>
<string name="button.callMeeting">Call Meeting</string>
<string name="button.winGame">Win Game</string>
<string name="modifier.captain">Captain</string>
<string name="modifier.captain.TabDescription">You can call a meeting from anywhere on the map.</string>
<string name="modifier.freezed">Freezed</string>
<string name="modifier.highPriority">High Priority</string>
<string name="modifier.highPriority.TabDescription">You are high priority! Idrk what this means either tbh...</string>
<string name="options.exampleOptions1">Example Options 1</string>
<string name="options.exampleOptions1.toggleOpt1">Toggle Opt 1</string>
<string name="options.exampleOptions1.toggleOpt2">Toggle Opt 2</string>
<string name="options.exampleOptions1.numberOpt">Number Opt</string>
<string name="options.exampleOptions1.bestApi">Best API</string>
<string name="options.exampleOptions1.worstApi">Worst API</string>
<string name="options.exampleOptions1.players">Choose A Player</string>
<string name="options.exampleOptions2">Example Options 2</string>
<string name="options.exampleOptions2.youMustChoseAPlayer">YOU MUST CHOSE A PLAYER</string>
<string name="options.exampleOptions2.ehWhatever">Eh whatever</string>
<string name="options.exampleOptions2.toggleOption1">Toggle Option 1</string>
<string name="options.exampleOptions2.toggleOption2">Toggle Option 2</string>
<string name="options.exampleOptions2.enumOpt">Enum Opt</string>
<string name="options.captainSettings">Captain Settings</string>
<string name="options.captainSettings.amount">Amount</string>
<string name="options.captainSettings.chance">Chance</string>
<string name="options.captainSettings.uses">Uses</string>
<string name="options.freezeModifierSettings">Freeze</string>
<string name="options.freezeModifierSettings.useColor">Use Color</string>
<string name="options.generalModifier">General</string>
<string name="options.generalModifier.someBooleanOption">Some Boolean Option</string>
<string name="options.generalModifier.numberOption">Number Option</string>
<string name="options.freezerRoleSettings">Custom Role</string>
<string name="ApiExample.Role.Freezer.Settings.FreezeDuration">Freeze Duration</string>
<string name="ApiExample.Role.Freezer.Settings.FreezeUses">Freeze Uses</string>
<string name="ApiExample.Role.Teleporter.Settings.TeleportCooldown">Teleport Cooldown</string>
<string name="ApiExample.Role.Teleporter.Settings.TeleportDuration">Teleport Duration</string>
<string name="ApiExample.Role.Teleporter.Settings.ZoomDistance">Zoom Distance</string>
<string name="settings.miraExample">Mira Example</string>
<string name="settings.miraExample.exampleBool">Example Bool</string>
<string name="settings.miraExample.exampleBool2">Example Bool 2</string>
<string name="settings.miraExample.slider">Slider</string>
<string name="settings.miraExample.exampleNumber">Example Number</string>
<string name="settings.miraExample.exampleMultiplier">Example Multiplier</string>
<string name="settings.miraExample.exampleEnum">Example Enum</string>
<string name="settings.miraExample.button.exampleButton">Example Button</string>
<string name="gameover.outcastKiller.win">Outcast Killer Wins!</string>
<string name="color.cerulean">Cerulean</string>
<string name="color.rose">Rose</string>
<string name="color.gold">Gold</string>
<string name="color.rainbow">Rainbow</string>
</resources>
@@ -0,0 +1,66 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="ApiExample.Role.Teleporter">传送者</string>
<string name="ApiExample.Role.Teleporter.IntroBlurb">缩小视野并在地图上传送!</string>
<string name="ApiExample.Role.Teleporter.MedDescription">缩小视野并在地图上传送!</string>
<string name="ApiExample.Role.Teleporter.TabDescription">缩小视野并在地图上传送!</string>
<string name="ApiExample.Role.Freezer">冰冻者</string>
<string name="ApiExample.Role.Freezer.TabDescription">将其他玩家冰冻一段时间。</string>
<string name="ApiExample.Role.Crewmate.Mayor">市长</string>
<string name="ApiExample.Role.Crewmate.Mayor.TabDescription">你拥有额外投票权!</string>
<string name="ApiExample.Role.Crewmate.Chamelon">变色龙</string>
<string name="ApiExample.Role.Crewmate.Chamelon.TabDescription">静止不动时保持隐身。</string>
<string name="ApiExample.Role.Outcast.OutcastKiller">流放杀手</string>
<string name="ApiExample.Role.Outcast.OutcastKiller.TabDescription">可以击杀的流放者。</string>
<string name="outcast">流放者</string>
<string name="outcast.roleTitle">流放者</string>
<string name="outcast.teamIntroDescription">你是流放者,你没有队伍。</string>
<string name="button.think">思考</string>
<string name="button.think.thinking">思考中...</string>
<string name="button.freeze">冰冻</string>
<string name="button.teleport">传送</string>
<string name="button.callMeeting">召集会议</string>
<string name="button.winGame">赢得游戏</string>
<string name="modifier.captain">队长</string>
<string name="modifier.captain.TabDescription">你可以在地图上任意位置召集会议。</string>
<string name="modifier.freezed">已被冰冻</string>
<string name="modifier.highPriority">高优先级</string>
<string name="modifier.highPriority.TabDescription">你是高优先级!老实说我也不太清楚这是什么意思……</string>
<string name="options.exampleOptions1">示例选项组 1</string>
<string name="options.exampleOptions1.toggleOpt1">开关选项 1</string>
<string name="options.exampleOptions1.toggleOpt2">开关选项 2</string>
<string name="options.exampleOptions1.numberOpt">数字选项</string>
<string name="options.exampleOptions1.bestApi">谁是最好的API</string>
<string name="options.exampleOptions2">示例选项组 2</string>
<string name="options.exampleOptions2.youMustChoseAPlayer">你必须选择一个玩家</string>
<string name="options.exampleOptions2.ehWhatever">咕咕嘎嘎</string>
<string name="options.exampleOptions2.toggleOption1">开关选项 1</string>
<string name="options.exampleOptions2.toggleOption2">开关选项 2</string>
<string name="options.exampleOptions2.enumOpt">枚举选项</string>
<string name="options.captainSettings">队长设置</string>
<string name="options.captainSettings.amount">数量</string>
<string name="options.captainSettings.chance">概率</string>
<string name="options.captainSettings.uses">使用次数</string>
<string name="options.freezeModifierSettings">冰冻</string>
<string name="options.freezeModifierSettings.useColor">使用颜色</string>
<string name="options.generalModifier">通用</string>
<string name="options.generalModifier.someBooleanOption">某个布尔选项</string>
<string name="options.generalModifier.numberOption">数字选项</string>
<string name="ApiExample.Role.Freezer.Settings.FreezeDuration">冰冻时长</string>
<string name="ApiExample.Role.Freezer.Settings.FreezeUses">冰冻次数</string>
<string name="ApiExample.Role.Teleporter.Settings.TeleportCooldown">传送冷却</string>
<string name="ApiExample.Role.Teleporter.Settings.TeleportDuration">传送持续时间</string>
<string name="ApiExample.Role.Teleporter.Settings.ZoomDistance">缩放距离</string>
<string name="settings.miraExample">Mira 示例插件</string>
<string name="settings.miraExample.exampleBool">示例布尔</string>
<string name="settings.miraExample.exampleBool2">示例布尔 2</string>
<string name="settings.miraExample.slider">滑块</string>
<string name="settings.miraExample.exampleNumber">示例数字</string>
<string name="settings.miraExample.exampleMultiplier">示例倍数</string>
<string name="settings.miraExample.exampleEnum">示例枚举</string>
<string name="settings.miraExample.button.exampleButton">示例按钮</string>
<string name="gameover.outcastKiller.win">流放杀手获胜!</string>
<string name="color.cerulean">天蓝</string>
<string name="color.rose">玫瑰红</string>
<string name="color.gold">金色</string>
</resources>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

+24 -17
View File
@@ -3,39 +3,39 @@ using MiraAPI.Patches.Stubs;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using TMPro;
using UnityEngine;
namespace MiraAPI.Example.Roles;
public class ChameloenRole : CrewmateRole, ICustomRole
public class ChameleonRole : CrewmateRole, ICustomRole
{
public string RoleName => "Chamelon";
public string RoleLongDescription => "Stay invisible while not moving.";
public string RoleDescription => RoleLongDescription;
private bool shouldHide;
public string IdPart => "Chameleon";
public string IdPrefix => "ApiExample.Role.Crewmate";
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
private bool _shouldHide;
public CustomRoleConfiguration Configuration => new(this)
{
OptionsScreenshot = ExampleAssets.Banner,
Icon = MiraAssets.CrewmateFile,
IconTmp = TmpSpriteUtils.CreateSpriteAsset(MiraAssets.CrewmateFile.LoadAsset(), "ApiExample.Role.Impostor.ChameleonRole"),
IconTmp = TmpSpriteUtils.CreateSpriteAsset(
MiraAssets.CrewmateFile.LoadAsset(),
"ApiExample.Role.Impostor.ChameleonRole"),
IntroSound = CustomRoleUtils.GetIntroSound(RoleTypes.Shapeshifter),
};
public override void Initialize(PlayerControl player)
{
Logger<ExamplePlugin>.Info("Initializing ChamelonRole for player: " + player.PlayerId);
Logger<ExamplePlugin>.Info("Initializing ChameleonRole for player: " + player.PlayerId);
RoleBehaviourStubs.Initialize(this, player);
_shouldHide = true;
shouldHide = true;
}
public void FixedUpdate()
{
if (!Player || !_shouldHide)
if (!Player || !shouldHide)
{
return;
}
@@ -54,27 +54,34 @@ public class ChameloenRole : CrewmateRole, ICustomRole
}
else
{
SpriteRenderer rend = Player.cosmetics.currentBodySprite.BodySprite;
TextMeshPro tmp = Player.cosmetics.nameText;
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
var rend = Player.cosmetics.currentBodySprite.BodySprite;
var tmp = Player.cosmetics.nameText;
tmp.color = Color.Lerp(
tmp.color,
new Color(tmp.color.r, tmp.color.g, tmp.color.b, Player.AmOwner ? 0.3f : 0),
Time.deltaTime * 4f);
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
{
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
cosmetic.color = Color.Lerp(
cosmetic.color,
new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0),
Time.deltaTime * 4f);
}
}
}
public override void Deinitialize(PlayerControl targetPlayer)
{
Logger<ExamplePlugin>.Info("Deinitializing ChamelonRole for player: " + targetPlayer.PlayerId);
Logger<ExamplePlugin>.Info("Deinitializing ChameleonRole for player: " + targetPlayer.PlayerId);
RoleBehaviourStubs.Deinitialize(this, targetPlayer);
_shouldHide = false;
shouldHide = false;
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>(true))
{
cosmetic.color = Color.white;
}
Player.cosmetics.currentBodySprite.BodySprite.color = Color.white;
Player.cosmetics.nameText.color = Color.white;
}
+4 -5
View File
@@ -6,16 +6,15 @@ namespace MiraAPI.Example.Roles;
public class FreezerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Freezer";
public string RoleLongDescription => "Freeze another player for a duration of time.";
public string RoleDescription => RoleLongDescription;
public string IdPart => "Freezer";
public string IdPrefix => "ApiExample.Role.Impostor";
public Color RoleColor => Palette.Blue;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
public CustomRoleConfiguration Configuration => new(this)
{
Icon = MiraAssets.ImpostorFile,
IconTmp = TmpSpriteUtils.CreateSpriteAsset(MiraAssets.ImpostorFile.LoadAsset(), "ApiExample.Role.Impostor.FreezerRole"),
IconTmp = TmpSpriteUtils.CreateSpriteAsset(MiraAssets.ImpostorFile.LoadAsset(), "ApiExample.Role.Impostor.Freezer"),
OptionsScreenshot = ExampleAssets.Banner,
MaxRoleCount = 2,
};
+2 -3
View File
@@ -5,9 +5,8 @@ namespace MiraAPI.Example.Roles;
public class MayorRole : CrewmateRole, ICustomRole
{
public string RoleName => "Mayor";
public string RoleLongDescription => "You get extra votes!";
public string RoleDescription => RoleLongDescription;
public string IdPart => "Mayor";
public string IdPrefix => "ApiExample.Role.Crewmate";
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public CustomRoleConfiguration Configuration => new(this);
+5 -6
View File
@@ -8,9 +8,8 @@ namespace MiraAPI.Example.Roles;
public class NeutralKillerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Outcast Killer";
public string RoleDescription => "Outcast who can kill.";
public string RoleLongDescription => RoleDescription;
public string IdPart => "OutcastKiller";
public string IdPrefix => "ApiExample.Role.Outcast";
public Color RoleColor => Color.magenta;
public ModdedRoleTeams Team => ModdedRoleTeams.Custom;
@@ -21,13 +20,13 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
CanUseVent = true,
};
public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray);
public RoleOptionsGroup RoleOptionsGroup { get; } = new("outcast.group", Color.gray);
[HideFromIl2Cpp]
public TeamIntroConfiguration? IntroConfiguration { get; } = new(
Color.gray,
"OUTCAST",
"You are an Outcast. You do not have a team.");
"outcast.roleTitle",
"outcast.teamIntroDescription");
public override void SpawnTaskHeader(PlayerControl playerControl)
{
+3 -4
View File
@@ -5,13 +5,12 @@ namespace MiraAPI.Example.Roles;
public class TeleporterRole : CrewmateRole, ICustomRole
{
public string RoleName => "Teleporter";
public string RoleLongDescription => "Zoom out and teleport across the map!";
public string RoleDescription => RoleLongDescription;
public string IdPart => "Teleporter";
public string IdPrefix => "ApiExample.Role.Crewmate";
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
public CustomRoleConfiguration Configuration => new(this)
{
OptionsScreenshot = ExampleAssets.Banner,
CanModifyChance = false,
+107
View File
@@ -0,0 +1,107 @@
using System;
using System.Diagnostics.CodeAnalysis;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Colors.ColorSpaces;
/// <summary>
/// A struct that denotes the CMYK (Cyan, Magenta, Yellow, Key) color space.
/// </summary>
/// <param name="c">The cyan component.</param>
/// <param name="m">The magenta component.</param>
/// <param name="y">The yellow component.</param>
/// <param name="k">The key component.</param>
/// <param name="a">The alpha component.</param>
[Serializable]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
public record struct CmykColor(float c, float m, float y, float k, float a = 1f)
{
/// <summary>
/// Implicitly converts a <see cref="CmykColor"/> to a Unity <see cref="Color"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator Color(CmykColor c)
{
return ToColor(c);
}
/// <summary>
/// Implicitly converts a Unity <see cref="Color"/> to a <see cref="CmykColor"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator CmykColor(Color c)
{
return FromColor(c);
}
/// <summary>
/// Converts a <see cref="CmykColor"/> to a standard Unity RGB <see cref="Color"/>.
/// </summary>
/// <param name="cmyk">The CMYK color to convert.</param>
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
public static Color ToColor(CmykColor cmyk)
{
var key = 1f - cmyk.k;
var r = (1f - cmyk.c) * key;
var g = (1f - cmyk.m) * key;
var b = (1f - cmyk.y) * key;
return new Color(MathUtilities.Clamp01(r), MathUtilities.Clamp01(g), MathUtilities.Clamp01(b), cmyk.a);
}
/// <summary>
/// Converts a standard Unity RGB <see cref="Color"/> to a <see cref="CmykColor"/>.
/// </summary>
/// <param name="color">The Unity Color to convert.</param>
/// <returns>A <see cref="CmykColor"/> representing the same visual color.</returns>
public static CmykColor FromColor(Color color)
{
var r = color.r;
var g = color.g;
var b = color.b;
var k = 1f - MathF.Max(r, MathF.Max(g, b));
if (k >= 1f)
return new CmykColor(0f, 0f, 0f, 1f, color.a);
var invK = 1f / (1f - k);
var c = (1f - r - k) * invK;
var m = (1f - g - k) * invK;
var y = (1f - b - k) * invK;
return new CmykColor(MathUtilities.Clamp01(c), MathUtilities.Clamp01(m), MathUtilities.Clamp01(y), MathUtilities.Clamp01(k), color.a);
}
/// <summary>
/// Linearly interpolates between two <see cref="CmykColor"/> values.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
/// <returns>The interpolated <see cref="CmykColor"/>.</returns>
public static CmykColor Lerp(CmykColor a, CmykColor b, float t)
{
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
}
/// <summary>
/// Linearly interpolates between two <see cref="CmykColor"/> values without clamping the interpolant.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors.</param>
/// <returns>The interpolated <see cref="CmykColor"/>.</returns>
public static CmykColor LerpUnclamped(CmykColor a, CmykColor b, float t)
{
return new CmykColor(
MathUtilities.LerpUnclamped(a.c, b.c, t),
MathUtilities.LerpUnclamped(a.m, b.m, t),
MathUtilities.LerpUnclamped(a.y, b.y, t),
MathUtilities.LerpUnclamped(a.k, b.k, t),
MathUtilities.LerpUnclamped(a.a, b.a, t));
}
}
+136
View File
@@ -0,0 +1,136 @@
using System;
using System.Diagnostics.CodeAnalysis;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Colors.ColorSpaces;
/// <summary>
/// A struct that denotes the HSB (Hue, Saturation, Brightness) color space.
/// </summary>
/// <param name="h">The hue component.</param>
/// <param name="s">The saturation component.</param>
/// <param name="b">The brightness component.</param>
/// <param name="a">The alpha component.</param>
[Serializable]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
public record struct HsbColor(float h, float s, float b, float a = 1f)
{
/// <summary>
/// Implicitly converts an <see cref="HsbColor"/> to a Unity <see cref="Color"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator Color(HsbColor c)
{
return ToColor(c);
}
/// <summary>
/// Implicitly converts a Unity <see cref="Color"/> to an <see cref="HsbColor"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator HsbColor(Color c)
{
return FromColor(c);
}
/// <summary>
/// Converts an <see cref="HsbColor"/> to a standard Unity RGB <see cref="Color"/>.
/// </summary>
/// <param name="hsb">The HSB color to convert.</param>
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
public static Color ToColor(HsbColor hsb)
{
if (hsb.s <= float.Epsilon)
return new Color(MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), hsb.a);
var c = hsb.b * hsb.s;
var hPrime = hsb.h * 6f;
var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f));
var m = hsb.b - c;
var (r, g, b) = hPrime switch
{
< 1f => (c, x, 0f),
< 2f => (x, c, 0f),
< 3f => (0f, c, x),
< 4f => (0f, x, c),
< 5f => (x, 0f, c),
<= 6f => (c, 0f, x),
_ => (0f, 0f, 0f),
};
return new Color(
MathUtilities.Clamp01(r + m),
MathUtilities.Clamp01(g + m),
MathUtilities.Clamp01(b + m),
hsb.a);
}
/// <summary>
/// Converts a standard Unity RGB <see cref="Color"/> to an <see cref="HsbColor"/>.
/// </summary>
/// <param name="color">The Unity Color to convert.</param>
/// <returns>An <see cref="HsbColor"/> representing the same visual color.</returns>
public static HsbColor FromColor(Color color)
{
var max = MathF.Max(color.r, MathF.Max(color.g, color.b));
var min = MathF.Min(color.r, MathF.Min(color.g, color.b));
var delta = max - min;
var h = 0f;
if (delta > 0f)
{
if (max == color.r)
h = (color.g - color.b) / delta % 6f;
else if (max == color.g)
h = (color.b - color.r) / delta + 2f;
else
h = (color.r - color.g) / delta + 4f;
h /= 6f;
if (h < 0f)
h += 1f;
}
var s = max == 0f ? 0f : delta / max;
var b = max;
return new HsbColor(h, s, b, color.a);
}
/// <summary>
/// Linearly interpolates between two <see cref="HsbColor"/> values, using the shortest path for the hue.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
/// <returns>The interpolated <see cref="HsbColor"/>.</returns>
public static HsbColor Lerp(HsbColor a, HsbColor b, float t)
{
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
}
/// <summary>
/// Linearly interpolates between two <see cref="HsbColor"/> values without clamping the interpolant.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors.</param>
/// <returns>The interpolated <see cref="HsbColor"/>.</returns>
public static HsbColor LerpUnclamped(HsbColor a, HsbColor b, float t)
{
var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f);
if (hueDifference > 0.5f)
hueDifference -= 1f;
return new HsbColor(
MathUtilities.Repeat(a.h + (hueDifference * t), 1f),
MathUtilities.LerpUnclamped(a.s, b.s, t),
MathUtilities.LerpUnclamped(a.b, b.b, t),
MathUtilities.LerpUnclamped(a.a, b.a, t));
}
}
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Diagnostics.CodeAnalysis;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Colors.ColorSpaces;
/// <summary>
/// A struct that denotes the HSL (Hue, Saturation, Lightness) color space.
/// </summary>
/// <param name="h">The hue component.</param>
/// <param name="s">The saturation component.</param>
/// <param name="l">The lightness component.</param>
/// <param name="a">The alpha component.</param>
[Serializable]
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")]
public record struct HslColor(float h, float s, float l, float a = 1f)
{
/// <summary>
/// Implicitly converts an <see cref="HslColor"/> to a Unity <see cref="Color"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator Color(HslColor c)
{
return ToColor(c);
}
/// <summary>
/// Implicitly converts a Unity <see cref="Color"/> to an <see cref="HslColor"/>.
/// </summary>
/// <param name="c">The color being converted.</param>
public static implicit operator HslColor(Color c)
{
return FromColor(c);
}
/// <summary>
/// Converts an <see cref="HslColor"/> to a standard Unity RGB <see cref="Color"/>.
/// </summary>
/// <param name="hsl">The HSL color to convert.</param>
/// <returns>A Unity <see cref="Color"/> representing the same visual color.</returns>
public static Color ToColor(HslColor hsl)
{
if (hsl.s <= float.Epsilon)
return new Color(MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), hsl.a);
var c = (1f - MathF.Abs(2f * hsl.l - 1f)) * hsl.s;
var hPrime = hsl.h * 6f;
var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f));
var m = hsl.l - c / 2f;
var (r, g, b) = hPrime switch
{
< 1f => (c, x, 0f),
< 2f => (x, c, 0f),
< 3f => (0f, c, x),
< 4f => (0f, x, c),
< 5f => (x, 0f, c),
<= 6f => (c, 0f, x),
_ => (0f, 0f, 0f),
};
return new Color(MathUtilities.Clamp01(r + m), MathUtilities.Clamp01(g + m), MathUtilities.Clamp01(b + m), hsl.a);
}
/// <summary>
/// Converts a standard Unity RGB <see cref="Color"/> to an <see cref="HslColor"/>.
/// </summary>
/// <param name="color">The Unity Color to convert.</param>
/// <returns>An <see cref="HslColor"/> representing the same visual color.</returns>
public static HslColor FromColor(Color color)
{
var max = MathF.Max(color.r, MathF.Max(color.g, color.b));
var min = MathF.Min(color.r, MathF.Min(color.g, color.b));
var l = (max + min) / 2f;
var delta = max - min;
var s = delta == 0f ? 0f : delta / (1f - MathF.Abs((2f * l) - 1f));
var h = 0f;
if (delta > 0f)
{
if (max == color.r)
h = (color.g - color.b) / delta % 6f;
else if (max == color.g)
h = (color.b - color.r) / delta + 2f;
else
h = (color.r - color.g) / delta + 4f;
h /= 6f;
if (h < 0f)
h += 1f;
}
return new HslColor(h, s, l, color.a);
}
/// <summary>
/// Linearly interpolates between two <see cref="HslColor"/> values, using the shortest path for the hue.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors, clamped to a 0 to 1 range.</param>
/// <returns>The interpolated <see cref="HslColor"/>.</returns>
public static HslColor Lerp(HslColor a, HslColor b, float t)
{
return LerpUnclamped(a, b, MathUtilities.Clamp01(t));
}
/// <summary>
/// Linearly interpolates between two <see cref="HslColor"/> values without clamping the interpolant.
/// </summary>
/// <param name="a">The starting color.</param>
/// <param name="b">The ending color.</param>
/// <param name="t">The interpolation value between the two colors.</param>
/// <returns>The interpolated <see cref="HslColor"/>.</returns>
public static HslColor LerpUnclamped(HslColor a, HslColor b, float t)
{
var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f);
if (hueDifference > 0.5f)
hueDifference -= 1f;
return new HslColor(
MathUtilities.Repeat(a.h + (hueDifference * t), 1f),
MathUtilities.LerpUnclamped(a.s, b.s, t),
MathUtilities.LerpUnclamped(a.l, b.l, t),
MathUtilities.LerpUnclamped(a.a, b.a, t));
}
}
+43 -11
View File
@@ -1,5 +1,6 @@
using MiraAPI.Utilities;
using Reactor.Localization.Utilities;
using System.Diagnostics.CodeAnalysis;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Colors;
@@ -10,17 +11,37 @@ namespace MiraAPI.Colors;
/// <param name="name">The name of the option.</param>
/// <param name="mainColor">The main color.</param>
/// <param name="shadowColor">The shadow color.</param>
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internal code.")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")]
public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
{
/// <summary>
/// Gets or sets the main color.
/// </summary>
public Color32 MainColor { get; set; } = mainColor;
public Color32 MainColor
{
get;
set
{
field = value;
InternalMainColor = value;
}
}
= mainColor;
/// <summary>
/// Gets or sets the shadow color.
/// </summary>
public Color32 ShadowColor { get; set; } = shadowColor;
public Color32 ShadowColor
{
get;
set
{
field = value;
InternalShadowColor = value;
}
}
= shadowColor;
/// <summary>
/// Gets or sets the name of the color.
@@ -32,18 +53,24 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
/// </summary>
public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker;
/// <inheritdoc />
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
internal Color InternalMainColor = mainColor;
internal Color InternalShadowColor = shadowColor;
/// <inheritdoc cref="CustomColor"/>
public CustomColor(StringNames name, Color32 mainColor)
: this(name, mainColor, mainColor.GetShadowColor(60))
{
}
/// <inheritdoc />
public CustomColor(string name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
/// <inheritdoc cref="CustomColor"/>
public CustomColor(string name, Color32 mainColor)
: this(name, mainColor, mainColor.GetShadowColor(60))
{
}
/// <inheritdoc />
public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(CustomStringName.CreateAndRegister(name), mainColor, shadowColor)
/// <inheritdoc cref="CustomColor"/>
public CustomColor(string name, Color32 mainColor, Color32 shadowColor)
: this(MiraLocaleManager.GetOrCreateLocaleString(name), mainColor, shadowColor)
{
}
}
@@ -53,6 +80,11 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
/// </summary>
public enum CustomColorBrightness
{
/// <summary>
/// An invalid color.
/// </summary>
Invalid = -1,
/// <summary>
/// A lighter color.
/// </summary>
+115
View File
@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using HarmonyLib;
using UnityEngine;
namespace MiraAPI.Colors;
@@ -15,9 +17,36 @@ public static class PaletteManager
public static CustomColor[] RegisteredColors => [.. CustomColors];
internal static readonly List<CustomColor> CustomColors = [];
internal static readonly HashSet<StringNames> VariableColorNames = [];
private static readonly CustomColorBrightness[] ColorBrightnessMap =
[
CustomColorBrightness.Darker, // Red
CustomColorBrightness.Darker, // Blue
CustomColorBrightness.Darker, // Green
CustomColorBrightness.Lighter, // Pink
CustomColorBrightness.Lighter, // Orange
CustomColorBrightness.Lighter, // Yellow
CustomColorBrightness.Darker, // Black
CustomColorBrightness.Lighter, // White
CustomColorBrightness.Darker, // Purple
CustomColorBrightness.Darker, // Brown
CustomColorBrightness.Lighter, // Cyan
CustomColorBrightness.Lighter, // Lime
CustomColorBrightness.Darker, // Maroon
CustomColorBrightness.Lighter, // Rose
CustomColorBrightness.Lighter, // Banana
CustomColorBrightness.Darker, // Gray
CustomColorBrightness.Darker, // Tan
CustomColorBrightness.Lighter, // Coral
];
private static int originalLength;
internal static void RegisterAllColors()
{
originalLength = Palette.PlayerColors.Length;
var colors = CustomColors.Select(x => x.MainColor).ToArray();
var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray();
var stringNames = CustomColors.Select(x => x.Name).ToArray();
@@ -29,4 +58,90 @@ public static class PaletteManager
Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors);
Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors);
}
/// <summary>
/// Checks to see if the provided color ID belongs to a variable color.
/// </summary>
/// <param name="colorId">The id of the color.</param>
/// <returns><c>true</c> if the color is a variable color; <c>false</c> otherwise.</returns>
public static bool IsVariable(int colorId)
{
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return false;
return VariableColorNames.Contains(Palette.ColorNames[colorId]);
}
/// <summary>
/// Checks to see if the provided color ID belongs to a custom color.
/// </summary>
/// <param name="colorId">The ID of the color.</param>
/// <returns><c>true</c> if the color is a custom color; <c>false</c> otherwise.</returns>
public static bool IsCustomColor(int colorId)
{
return (uint)(colorId - originalLength) < (uint)CustomColors.Count;
}
/// <summary>
/// Gets the main color of the provided color ID.
/// </summary>
/// <param name="colorId">The id of the color.</param>
/// <returns>The main color associated with the provided ID.</returns>
public static Color GetMainColor(int colorId)
{
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return Color.white;
return !TryGetCustomColor(colorId, out var custom)
? Palette.PlayerColors[colorId]
: (custom is VariableColor variable
? variable.EvaluateMainColor(Time.time)
: custom.InternalMainColor);
}
/// <summary>
/// Gets the shadow color of the provided color ID.
/// </summary>
/// <param name="colorId">The ID of the color.</param>
/// <returns>The shadow color associated with the provided ID.</returns>
public static Color GetShadowColor(int colorId)
{
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return Color.white;
return !TryGetCustomColor(colorId, out var custom)
? Palette.ShadowColors[colorId]
: (custom is VariableColor variable
? variable.EvaluateShadowColor(Time.time)
: custom.InternalShadowColor);
}
/// <summary>
/// Gets the brightness type of the color associated with the provided ID.
/// </summary>
/// <param name="colorId">The ID of the color.</param>
/// <returns>The brightness type of the provided ID.</returns>
public static CustomColorBrightness GetBrightness(int colorId)
{
if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return CustomColorBrightness.Invalid;
return TryGetCustomColor(colorId, out var custom)
? custom.ColorBrightness
: ColorBrightnessMap[colorId];
}
internal static bool TryGetCustomColor(int colorId, [NotNullWhen(true)] out CustomColor? custom)
{
var index = colorId - originalLength;
if ((uint)index >= (uint)CustomColors.Count)
{
custom = null;
return false;
}
custom = CustomColors[index];
return true;
}
}
+72
View File
@@ -0,0 +1,72 @@
using System;
using MiraAPI.LocalSettings;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Colors;
/// <summary>
/// Represents a dynamic custom color that changes over time.
/// </summary>
/// <param name="name">The name of the color.</param>
/// <param name="mainEvaluator">The function to evaluate the dynamic main color.</param>
/// <param name="shadowEvaluator">The function to evaluate the dynamic shadow color.</param>
/// <param name="mainColor">The static main color used when dynamic colors are disabled.</param>
/// <param name="shadowColor">The static shadow color used when dynamic colors are disabled.</param>
/// <param name="defaultTime">The default time that is used to automatically fill in the static colors when the latter is not provided.</param>
public sealed class VariableColor(StringNames name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
{
/// <summary>
/// Gets or sets a delegate that takes in a time value and returns the dynamic main color.
/// </summary>
public Func<float, Color> MainEvaluator { get; set; } = mainEvaluator;
/// <summary>
/// Gets or sets a delegate that takes in a time value and returns the dynamic shadow color.
/// </summary>
public Func<float, Color> ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f));
/// <inheritdoc cref="VariableColor"/>
public VariableColor(StringNames name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
: this(name, mainEvaluator, null, mainColor, null, defaultTime)
{
}
/// <inheritdoc cref="VariableColor"/>
public VariableColor(string name, Func<float, Color> mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
: this(name, mainEvaluator, null, mainColor, null, defaultTime)
{
}
/// <inheritdoc cref="VariableColor"/>
public VariableColor(string name, Func<float, Color> mainEvaluator, Func<float, Color>? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
: this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor, defaultTime)
{
}
/// <summary>
/// Evaluates the dynamic main color based on the provided time value.
/// </summary>
/// <param name="time">The time to evaluate the color against.</param>
/// <returns>The dynamically evaluated color, or the static <see cref="CustomColor.MainColor"/> if static variable colors are enabled in the user's settings.</returns>
public Color EvaluateMainColor(float time)
{
return LocalSettingsTabSingleton<MiraApiSettings>.Instance.EnableVariableColors.Value
? MainEvaluator(time)
: InternalMainColor;
}
/// <summary>
/// Evaluates the dynamic shadow color based on the provided time value.
/// </summary>
/// <param name="time">The time to evaluate the color against.</param>
/// <returns>The dynamically evaluated color, or the static <see cref="CustomColor.ShadowColor"/> if static variable colors are enabled in the user's settings.</returns>
public Color EvaluateShadowColor(float time)
{
return LocalSettingsTabSingleton<MiraApiSettings>.Instance.EnableVariableColors.Value
? ShadowEvaluator(time)
: InternalShadowColor;
}
}
+102
View File
@@ -0,0 +1,102 @@
using System.Diagnostics.CodeAnalysis;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Colors;
using MiraAPI.Utilities;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI;
/// <summary>
/// The base class for variable color behaviour on <see cref="SpriteRenderer"/>s.
/// </summary>
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity fields.")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")]
public abstract class BaseVariableColorBehaviour : MonoBehaviour
{
/// <summary>
/// The <see cref="VariableColor"/> to apply.
/// </summary>
protected VariableColor? color;
/// <summary>
/// Sets the current variable color.
/// </summary>
/// <param name="newColor">The new <see cref="VariableColor"/>.</param>
[HideFromIl2Cpp]
public void SetColor(VariableColor? newColor)
{
color = newColor;
}
}
/// <summary>
/// A mono script that adjusts the material colors of a <see cref="Renderer"/> component that it's attached to based on the provided variable color.
/// </summary>
[RegisterInIl2Cpp]
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
public sealed class VariableColorBehaviour : BaseVariableColorBehaviour
{
private Renderer? rend;
private static readonly Color VisorColor = Palette.VisorColor;
/// <summary>
/// Sets the new <see cref="Renderer"/> target.
/// </summary>
/// <param name="newRend">The new <see cref="Renderer"/> that the script must target.</param>
public void SetRend(Renderer? newRend)
{
rend = newRend;
}
private void Awake()
{
rend = GetComponent<Renderer>();
}
private void Update()
{
if (color == null || rend == null)
return;
var time = Time.time;
var material = rend.material;
material.SetColor(ShaderID.VisorColor, VisorColor);
material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time));
material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time));
}
}
/// <summary>
/// A mono script that adjusts the graphic colors of a <see cref="SpriteRenderer"/> component that it's attached to based on the provided variable color.
/// </summary>
[RegisterInIl2Cpp]
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")]
public sealed class BasicVariableColorBehaviour : BaseVariableColorBehaviour
{
private SpriteRenderer? rend;
/// <summary>
/// Sets the new <see cref="SpriteRenderer"/> target.
/// </summary>
/// <param name="newRend">The new <see cref="SpriteRenderer"/> that the script must target.</param>
public void SetRend(SpriteRenderer? newRend)
{
rend = newRend;
}
private void Awake()
{
rend = GetComponent<SpriteRenderer>();
}
private void Update()
{
if (color != null && rend != null)
rend.color = color.EvaluateMainColor(Time.time);
}
}
@@ -6,39 +6,24 @@ namespace MiraAPI.Events.Mira;
/// Invoked when a <see cref="CustomActionButton"/> click is cancelled. Do not use for vanilla <see cref="AbilityButton"/>s.
/// </summary>
/// <typeparam name="T">The <see cref="CustomActionButton"/> type.</typeparam>
public sealed class MiraButtonCancelledEvent<T> : MiraEvent where T : CustomActionButton
/// <param name="button">The <see cref="CustomActionButton"/> whose click was cancelled.</param>
public sealed class MiraButtonCancelledEvent<T>(T button) : MiraEvent
where T : CustomActionButton
{
/// <summary>
/// Gets the <see cref="CustomActionButton"/> whose click was cancelled.
/// </summary>
public T Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonCancelledEvent{T}"/> class.
/// </summary>
/// <param name="button">The <see cref="CustomActionButton"/> whose click was cancelled.</param>
public MiraButtonCancelledEvent(T button)
{
Button = button;
}
public T Button { get; } = button;
}
/// <summary>
/// Invoked when a <see cref="CustomActionButton"/> click is cancelled. Do not use for vanilla <see cref="AbilityButton"/>s.
/// </summary>
public sealed class MiraButtonCancelledEvent : MiraEvent
/// <param name="button">The <see cref="CustomActionButton"/> whose click was cancelled.</param>
public sealed class MiraButtonCancelledEvent(CustomActionButton button) : MiraEvent
{
/// <summary>
/// Gets the <see cref="CustomActionButton"/> whose click was cancelled.
/// </summary>
public CustomActionButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonCancelledEvent"/> class.
/// </summary>
/// <param name="button">The <see cref="CustomActionButton"/> whose click was cancelled.</param>
public MiraButtonCancelledEvent(CustomActionButton button)
{
Button = button;
}
public CustomActionButton Button { get; } = button;
}
+9 -25
View File
@@ -6,46 +6,30 @@ namespace MiraAPI.Events.Mira;
/// Button click event for <see cref="CustomActionButton"/>s only. Do not use for vanilla <see cref="AbilityButton"/>s.
/// </summary>
/// <typeparam name="T">The <see cref="CustomActionButton"/> type.</typeparam>
public sealed class MiraButtonClickEvent<T> : MiraCancelableEvent where T : CustomActionButton
/// <param name="button">The <see cref="CustomActionButton"/> that was clicked.</param>
/// <param name="genericClickEvent">The generic <see cref="MiraButtonClickEvent"/> invoked before button-specific events.</param>
public class MiraButtonClickEvent<T>(T button, MiraButtonClickEvent genericClickEvent) : MiraCancelableEvent
where T : CustomActionButton
{
/// <summary>
/// Gets the <see cref="CustomActionButton"/> that was clicked.
/// </summary>
public T Button { get; }
public T Button { get; } = button;
/// <summary>
/// Gets the generic <see cref="MiraButtonClickEvent"/> that is invoked before this button-specific event.
/// </summary>
public MiraButtonClickEvent GenericClickEvent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonClickEvent{T}"/> class.
/// </summary>
/// <param name="button">The <see cref="CustomActionButton"/> that was clicked.</param>
/// <param name="genericClickEvent">The generic <see cref="MiraButtonClickEvent"/> invoked before button-specific events.</param>
public MiraButtonClickEvent(T button, MiraButtonClickEvent genericClickEvent)
{
GenericClickEvent = genericClickEvent;
Button = button;
}
public MiraButtonClickEvent GenericClickEvent { get; } = genericClickEvent;
}
/// <summary>
/// Button click event for <see cref="CustomActionButton"/>s only. Do not use for vanilla <see cref="AbilityButton"/>s.
/// </summary>
public sealed class MiraButtonClickEvent : MiraCancelableEvent
/// <param name="button">The <see cref="CustomActionButton"/> that was clicked.</param>
public class MiraButtonClickEvent(CustomActionButton button) : MiraCancelableEvent
{
/// <summary>
/// Gets the <see cref="CustomActionButton"/> that was clicked.
/// </summary>
public CustomActionButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonClickEvent"/> class.
/// </summary>
/// <param name="button">The <see cref="CustomActionButton"/> that was clicked.</param>
public MiraButtonClickEvent(CustomActionButton button)
{
Button = button;
}
public CustomActionButton Button { get; } = button;
}
@@ -0,0 +1,21 @@
using UnityEngine;
namespace MiraAPI.Events.Mira;
/// <summary>
/// Event fired by Mira API to adjust the UI buttons at the top right of the screen.
/// </summary>
/// <param name="topUiRow">The <see cref="GameObject"/> for the top UI row.</param>
/// <param name="secondUiRow">The <see cref="GameObject"/> for the second UI row.</param>
public class UiButtonPostResetEvent(GameObject topUiRow, GameObject secondUiRow) : MiraEvent
{
/// <summary>
/// Gets the <see cref="GameObject"/> parent for the very top UI row.
/// </summary>
public GameObject MainTopUiRow { get; } = topUiRow;
/// <summary>
/// Gets the <see cref="GameObject"/> parent for the second top UI row.
/// </summary>
public GameObject SecondTopUiRow { get; } = secondUiRow;
}
+15
View File
@@ -0,0 +1,15 @@
namespace MiraAPI.Events.Mira;
/// <summary>
/// Event fired by Mira API prior to adjusting the UI buttons at the top right of the screen.
/// </summary>
public class UiButtonResetEvent : MiraEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="UiButtonResetEvent"/> class.
/// </summary>
public UiButtonResetEvent()
{
// nothing!
}
}
+8 -3
View File
@@ -17,9 +17,11 @@ public static class MiraEventManager
/// <param name="eventInstance">The <typeparamref name="T"/> instance.</param>
/// <typeparam name="T">Type of Event.</typeparam>
/// <returns>If there was an event handler invoked for this event, return <see langword="true"/>. Otherwise, return <see langword="false"/>.</returns>
public static bool InvokeEvent<T>(T eventInstance) where T : MiraEvent
public static bool InvokeEvent<T>(this T eventInstance)
where T : MiraEvent
{
EventWrappers.TryGetValue(typeof(T), out var handlers);
if (handlers == null || handlers.Count == 0)
{
return false;
@@ -46,9 +48,10 @@ public static class MiraEventManager
/// <param name="eventInstance">The <see cref="MiraEvent"/> instance.</param>
/// <param name="type">The type to use for handler lookup.</param>
/// <returns>If there was an event handler invoked for this event, return <see langword="true"/>. Otherwise, return <see langword="false"/>.</returns>
public static bool InvokeEvent(MiraEvent eventInstance, Type type)
public static bool InvokeEvent(this MiraEvent eventInstance, Type type)
{
EventWrappers.TryGetValue(type, out var handlers);
if (handlers == null || handlers.Count == 0)
{
return false;
@@ -107,7 +110,8 @@ public static class MiraEventManager
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
/// <typeparam name="T">Type of <see cref="MiraEvent"/> event.</typeparam>
/// <returns>A <see cref="MiraEventHandle"/> to use when unregistering the event.</returns>
public static MiraEventHandle RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
public static MiraEventHandle RegisterEventHandler<T>(Action<T> handler, int priority = 0)
where T : MiraEvent
{
EventWrappers.TryAdd(typeof(T), []);
@@ -146,6 +150,7 @@ public static class MiraEventManager
{
EventWrappers.Remove(eventHandle.EventType);
}
return true;
}
}
+5 -14
View File
@@ -5,26 +5,17 @@ namespace MiraAPI.Events;
/// <summary>
/// Wrapper for <see cref="MiraEvent"/> handlers.
/// </summary>
public class MiraEventWrapper
/// <param name="eventHandler">The action for the event handler.</param>
/// <param name="priority">The priority of the handler.</param>
public class MiraEventWrapper(Delegate eventHandler, int priority)
{
/// <summary>
/// Gets the event handler delegate.
/// </summary>
public Delegate EventHandler { get; }
public Delegate EventHandler { get; } = eventHandler;
/// <summary>
/// Gets the priority of the handler. Lower values are called first.
/// </summary>
public int Priority { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraEventWrapper"/> class.
/// </summary>
/// <param name="eventHandler">The action for the event handler.</param>
/// <param name="priority">The priority of the handler.</param>
public MiraEventWrapper(Delegate eventHandler, int priority)
{
EventHandler = eventHandler;
Priority = priority;
}
public int Priority { get; } = priority;
}
+3 -11
View File
@@ -5,20 +5,12 @@ namespace MiraAPI.Events;
/// <summary>
/// Register a <see cref="MiraEvent"/> handler.
/// </summary>
/// <param name="priority">The priority of the event. Lower values are called first.</param>
[AttributeUsage(AttributeTargets.Method)]
public class RegisterEventAttribute : Attribute
public class RegisterEventAttribute(int priority = 0) : Attribute
{
/// <summary>
/// Gets the priority of the event. Lower values are called first.
/// </summary>
public int Priority { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RegisterEventAttribute"/> class.
/// </summary>
/// <param name="priority">The priority of the event. Lower values are called first.</param>
public RegisterEventAttribute(int priority = 0)
{
Priority = priority;
}
public int Priority { get; } = priority;
}
@@ -3,38 +3,44 @@
/// <summary>
/// Event that is invoked after a player is murdered. Only called after a successful murder. This event is not cancelable.
/// </summary>
public class AfterMurderEvent : MiraEvent
/// <param name="source">The <see cref="PlayerControl"/> that killed the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that was killed.</param>
/// <param name="framed">The <see cref="PlayerControl"/> that was framed.</param>
/// <param name="deadBody">The <paramref name="target"/>'s <see cref="global::DeadBody"/>, if it exists.</param>
/// <param name="isIndirect">Whether the murder was caused indirectly.</param>
/// <param name="ignoreDefense">Whether the murder ignored protection.</param>
public class AfterMurderEvent(PlayerControl source, PlayerControl target, PlayerControl? framed, DeadBody? deadBody, bool isIndirect = true, bool ignoreDefense = false) : MiraEvent
{
/// <summary>
/// Gets the <see cref="PlayerControl"/> that killed the <see cref="Target"/>.
/// </summary>
public PlayerControl Source { get; }
public PlayerControl Source { get; } = source;
/// <summary>
/// Gets the <see cref="PlayerControl"/> that was killed.
/// </summary>
public PlayerControl Target { get; }
public PlayerControl Target { get; } = target;
/// <summary>
/// Gets the <see cref="PlayerControl"/> that was framed, if any.
/// </summary>
public PlayerControl? Framed { get; }
public PlayerControl? Framed { get; } = framed;
/// <summary>
/// Gets a value indicating whether the murder was done indirectly.
/// This is commonly used to prevent the <see cref="Source"/> <see cref="PlayerControl"/> from being killed.
/// </summary>
public bool IsIndirectAttack { get; }
public bool IsIndirectAttack { get; } = isIndirect;
/// <summary>
/// Gets a value indicating whether the murder was able to bypass protective roles or general defense, such as <see cref="GuardianAngelRole"/>.
/// </summary>
public bool IgnoreDefense { get; }
public bool IgnoreDefense { get; } = ignoreDefense;
/// <summary>
/// Gets the <see cref="Target"/>'s <see cref="DeadBody"/>, if it exists.
/// </summary>
public DeadBody? DeadBody { get; }
public DeadBody? DeadBody { get; } = deadBody;
/// <summary>
/// Initializes a new instance of the <see cref="AfterMurderEvent"/> class.
@@ -45,32 +51,8 @@ public class AfterMurderEvent : MiraEvent
/// <param name="isIndirect">Whether the murder was caused indirectly.</param>
/// <param name="ignoreDefense">Whether the murder ignored protection.</param>
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody, bool isIndirect, bool ignoreDefense = false)
: this(source, target, null, deadBody, isIndirect, ignoreDefense)
{
Source = source;
Target = target;
Framed = null;
DeadBody = deadBody;
IsIndirectAttack = isIndirect;
IgnoreDefense = ignoreDefense;
}
/// <summary>
/// Initializes a new instance of the <see cref="AfterMurderEvent"/> class.
/// </summary>
/// <param name="source">The <see cref="PlayerControl"/> that killed the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that was killed.</param>
/// <param name="framed">The <see cref="PlayerControl"/> that was framed.</param>
/// <param name="deadBody">The <paramref name="target"/>'s <see cref="DeadBody"/>, if it exists.</param>
/// <param name="isIndirect">Whether the murder was caused indirectly.</param>
/// <param name="ignoreDefense">Whether the murder ignored protection.</param>
public AfterMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, DeadBody? deadBody, bool isIndirect = true, bool ignoreDefense = false)
{
Source = source;
Target = target;
Framed = framed;
DeadBody = deadBody;
IsIndirectAttack = isIndirect;
IgnoreDefense = ignoreDefense;
}
/// <summary>
@@ -80,12 +62,7 @@ public class AfterMurderEvent : MiraEvent
/// <param name="target">The <see cref="PlayerControl"/> that was killed.</param>
/// <param name="deadBody">The <paramref name="target"/>'s <see cref="DeadBody"/>, if it exists.</param>
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody)
: this(source, target, null, deadBody)
{
Source = source;
Target = target;
Framed = null;
DeadBody = deadBody;
IsIndirectAttack = false;
IgnoreDefense = false;
}
}
@@ -3,19 +3,11 @@
/// <summary>
/// Invoked before <see cref="GameManager.RpcEndGame"/> is called, allowing cancellation of the game end entirely.
/// </summary>
public class BeforeGameEndEvent : MiraCancelableEvent
/// <param name="reason">The reason for the game end.</param>
public class BeforeGameEndEvent(GameOverReason reason) : MiraCancelableEvent
{
/// <summary>
/// Gets the reason for the game end.
/// </summary>
public GameOverReason Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeGameEndEvent"/> class.
/// </summary>
/// <param name="reason">The reason for the game end.</param>
public BeforeGameEndEvent(GameOverReason reason)
{
Reason = reason;
}
public GameOverReason Reason { get; } = reason;
}
@@ -5,38 +5,56 @@ namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked before a player is murdered. This event is cancelable.
/// </summary>
public sealed class BeforeMurderEvent : MiraCancelableEvent
/// <param name="source">The <see cref="PlayerControl"/> that is killing the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that is being killed.</param>
/// <param name="framed">The <see cref="PlayerControl"/> that is being framed.</param>
/// <param name="isIndirect">Whether the murder is caused indirectly.</param>
/// <param name="ignoreDefense">Whether the murder should ignore protection.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public sealed class BeforeMurderEvent(PlayerControl source, PlayerControl target, PlayerControl? framed, bool isIndirect = true, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore)
: MiraCancelableEvent
{
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is killing the <see cref="Target"/>.
/// </summary>
public PlayerControl Source { get; }
public PlayerControl Source { get; } = source;
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is being killed.
/// </summary>
public PlayerControl Target { get; }
public PlayerControl Target { get; } = target;
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is being framed, if any.
/// </summary>
public PlayerControl? Framed { get; }
public PlayerControl? Framed { get; } = framed;
/// <summary>
/// Gets or sets a value indicating whether the murder is done indirectly.
/// This is commonly used to prevent the <see cref="Source"/> <see cref="PlayerControl"/> from being killed.
/// </summary>
public bool IsIndirectAttack { get; set; }
public bool IsIndirectAttack { get; set; } = isIndirect;
/// <summary>
/// Gets or sets a value indicating whether the murder should bypass protective roles or general defense, such as <see cref="GuardianAngelRole"/>.
/// </summary>
public bool IgnoreDefense { get; set; }
public bool IgnoreDefense { get; set; } = ignoreDefense;
/// <summary>
/// Gets whether the murder was meant to be done in a meeting, via an enum.
/// </summary>
public MeetingCheck InMeeting { get; }
public MeetingCheck InMeeting { get; } = inMeeting;
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The <see cref="PlayerControl"/> that is killing the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that is being killed.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingCheck inMeeting = MeetingCheck.Ignore)
: this(source, target, false, false, inMeeting)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
@@ -47,62 +65,7 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
/// <param name="ignoreDefense">Whether the murder should ignore protection.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, bool isIndirect, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore)
: this(source, target, null, isIndirect, ignoreDefense, inMeeting)
{
Source = source;
Target = target;
Framed = null;
IsIndirectAttack = isIndirect;
IgnoreDefense = ignoreDefense;
InMeeting = inMeeting;
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The <see cref="PlayerControl"/> that is killing the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that is being killed.</param>
/// <param name="framed">The <see cref="PlayerControl"/> that is being framed.</param>
/// <param name="isIndirect">Whether the murder is caused indirectly.</param>
/// <param name="ignoreDefense">Whether the murder should ignore protection.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, bool isIndirect = true, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore)
{
Source = source;
Target = target;
Framed = framed;
IsIndirectAttack = isIndirect;
IgnoreDefense = ignoreDefense;
InMeeting = inMeeting;
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The <see cref="PlayerControl"/> that is killing the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that is being killed.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingCheck inMeeting)
{
Source = source;
Target = target;
Framed = null;
IsIndirectAttack = false;
IgnoreDefense = false;
InMeeting = inMeeting;
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The <see cref="PlayerControl"/> that is killing the <paramref name="target"/>.</param>
/// <param name="target">The <see cref="PlayerControl"/> that is being killed.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target)
{
Source = source;
Target = target;
Framed = null;
IsIndirectAttack = false;
IgnoreDefense = false;
InMeeting = MeetingCheck.Ignore;
}
}
@@ -1,22 +1,13 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event invoked on <see cref="IntroCutscene.OnDestroy"/> and <see cref="ExileController.WrapUp"/>
/// to determine if it should run the <see cref="RoundStartEvent"/> event or not.
/// Event invoked on <see cref="IntroCutscene.OnDestroy"/> and <see cref="ExileController.WrapUp"/> to determine if it should run the <see cref="RoundStartEvent"/> event or not.
/// </summary>
public class BeforeRoundStartEvent : MiraCancelableEvent
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public class BeforeRoundStartEvent(bool triggeredByIntro) : MiraCancelableEvent
{
/// <summary>
/// Gets a value indicating whether the event was triggered by the <see cref="IntroCutscene"/> or <see cref="ExileController"/>.
/// </summary>
public bool TriggeredByIntro { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeRoundStartEvent"/> class.
/// </summary>
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public BeforeRoundStartEvent(bool triggeredByIntro)
{
TriggeredByIntro = triggeredByIntro;
}
public bool TriggeredByIntro { get; } = triggeredByIntro;
}
@@ -1,21 +1,13 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the end game screen is shown. Non cancelable.
/// The event that is invoked when the end game screen is shown. Non-cancelable.
/// </summary>
public class GameEndEvent : MiraEvent
/// <param name="manager">The <see cref="global::EndGameManager"/> instance.</param>
public class GameEndEvent(EndGameManager manager) : MiraEvent
{
/// <summary>
/// Gets the <see cref="global::EndGameManager"/> instance.
/// </summary>
public EndGameManager EndGameManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GameEndEvent"/> class.
/// </summary>
/// <param name="manager">The <see cref="global::EndGameManager"/> instance.</param>
public GameEndEvent(EndGameManager manager)
{
EndGameManager = manager;
}
public EndGameManager EndGameManager { get; } = manager;
}
@@ -1,21 +1,13 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the intro cutscene is shown. Non cancelable.
/// The event that is invoked when the intro cutscene is shown. Non-cancelable.
/// </summary>
public class IntroBeginEvent : MiraEvent
/// <param name="introCutscene">The intro cutscene.</param>
public class IntroBeginEvent(IntroCutscene introCutscene) : MiraEvent
{
/// <summary>
/// Gets the <see cref="global::IntroCutscene"/> instance.
/// </summary>
public IntroCutscene IntroCutscene { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IntroBeginEvent"/> class.
/// </summary>
/// <param name="introCutscene">The intro cutscene.</param>
public IntroBeginEvent(IntroCutscene introCutscene)
{
IntroCutscene = introCutscene;
}
public IntroCutscene IntroCutscene { get; } = introCutscene;
}
@@ -3,6 +3,7 @@
/// <summary>
/// The event that is invoked when the intro cutscene has finished playing.
/// </summary>
/// <param name="introCutscene">The intro cutscene.</param>
public class IntroEndEvent(IntroCutscene introCutscene) : MiraEvent
{
/// <summary>
@@ -1,21 +1,13 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the player's role is shown on the intro cutscene. Non cancelable.
/// The event that is invoked when the player's role is shown on the intro cutscene. Non-cancelable.
/// </summary>
public class IntroRoleRevealEvent : MiraEvent
/// <param name="introCutscene">The intro cutscene.</param>
public class IntroRoleRevealEvent(IntroCutscene introCutscene) : MiraEvent
{
/// <summary>
/// Gets the <see cref="global::IntroCutscene"/> instance.
/// </summary>
public IntroCutscene IntroCutscene { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IntroRoleRevealEvent"/> class.
/// </summary>
/// <param name="introCutscene">The intro cutscene.</param>
public IntroRoleRevealEvent(IntroCutscene introCutscene)
{
IntroCutscene = introCutscene;
}
public IntroCutscene IntroCutscene { get; } = introCutscene;
}
@@ -4,19 +4,11 @@
/// Start Round event, invoked on <see cref="IntroCutscene.OnDestroy"/> and <see cref="ExileController.WrapUp"/>
/// if the <see cref="BeforeRoundStartEvent"/> isn't cancelled.
/// </summary>
public class RoundStartEvent : MiraEvent
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public class RoundStartEvent(bool triggeredByIntro) : MiraEvent
{
/// <summary>
/// Gets a value indicating whether the event was triggered by the <see cref="IntroCutscene"/> or <see cref="ExileController"/>.
/// </summary>
public bool TriggeredByIntro { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RoundStartEvent"/> class.
/// </summary>
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public RoundStartEvent(bool triggeredByIntro)
{
TriggeredByIntro = triggeredByIntro;
}
public bool TriggeredByIntro { get; } = triggeredByIntro;
}
@@ -5,26 +5,17 @@ namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked after a player's role is set. This event is not cancelable.
/// </summary>
public class SetRoleEvent : MiraEvent
/// <param name="player">The player.</param>
/// <param name="role">The new role.</param>
public class SetRoleEvent(PlayerControl player, RoleTypes role) : MiraEvent
{
/// <summary>
/// Gets the <see cref="PlayerControl"/> whose role was changed.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the role that the player was set to.
/// </summary>
public RoleTypes Role { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SetRoleEvent"/> class.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="role">The new role.</param>
public SetRoleEvent(PlayerControl player, RoleTypes role)
{
Player = player;
Role = role;
}
public RoleTypes Role { get; } = role;
}
@@ -6,39 +6,24 @@ namespace MiraAPI.Events.Vanilla.Gameplay;
/// Invoked when a Vanilla <see cref="AbilityButton"/> click is cancelled. Do not use for <see cref="CustomActionButton"/>.
/// </summary>
/// <typeparam name="T">The vanilla <see cref="AbilityButton"/> type.</typeparam>
public sealed class VanillaButtonCancelledEvent<T> : MiraEvent where T : AbilityButton
/// <param name="button">The vanilla <see cref="AbilityButton"/> whose click was cancelled.</param>
public sealed class VanillaButtonCancelledEvent<T>(T button) : MiraEvent
where T : AbilityButton
{
/// <summary>
/// Gets the Vanilla <see cref="AbilityButton"/> whose click was cancelled.
/// </summary>
public T Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent{T}"/> class.
/// </summary>
/// <param name="button">The vanilla <see cref="AbilityButton"/> whose click was cancelled.</param>
public VanillaButtonCancelledEvent(T button)
{
Button = button;
}
public T Button { get; } = button;
}
/// <summary>
/// Invoked when a Vanilla <see cref="AbilityButton"/> click is cancelled. Do not use for <see cref="CustomActionButton"/>s.
/// </summary>
public sealed class VanillaButtonCancelledEvent : MiraEvent
/// <param name="button">The vanilla <see cref="AbilityButton"/> whose click was cancelled.</param>
public sealed class VanillaButtonCancelledEvent(AbilityButton button) : MiraEvent
{
/// <summary>
/// Gets the Vanilla <see cref="AbilityButton"/> whose click was cancelled.
/// </summary>
public AbilityButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent"/> class.
/// </summary>
/// <param name="button">The vanilla <see cref="AbilityButton"/> whose click was cancelled.</param>
public VanillaButtonCancelledEvent(AbilityButton button)
{
Button = button;
}
public AbilityButton Button { get; } = button;
}
@@ -6,60 +6,42 @@ namespace MiraAPI.Events.Vanilla.Gameplay;
/// Button click event for Vanilla <see cref="AbilityButton"/>s only. Do not use for <see cref="CustomActionButton"/>s.
/// </summary>
/// <typeparam name="T">The vanilla <see cref="AbilityButton"/> type.</typeparam>
public sealed class VanillaButtonClickEvent<T> : MiraCancelableEvent where T : AbilityButton
/// <param name="button">The vanilla <see cref="AbilityButton"/> that was clicked.</param>
/// <param name="genericClickEvent">The generic <see cref="VanillaButtonClickEvent"/> invoked before button-specific events.</param>
public sealed class VanillaButtonClickEvent<T>(T button, VanillaButtonClickEvent genericClickEvent) : MiraCancelableEvent
where T : AbilityButton
{
/// <summary>
/// Gets the vanilla <see cref="AbilityButton"/> that was clicked.
/// </summary>
public T Button { get; }
public T Button { get; } = button;
/// <summary>
/// Gets the generic <see cref="VanillaButtonClickEvent"/> that is invoked before this button-specific event.
/// </summary>
public VanillaButtonClickEvent GenericClickEvent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent{T}"/> class.
/// </summary>
/// <param name="button">The vanilla <see cref="AbilityButton"/> that was clicked.</param>
/// <param name="genericClickEvent">The generic <see cref="VanillaButtonClickEvent"/> invoked before button-specific events.</param>
public VanillaButtonClickEvent(T button, VanillaButtonClickEvent genericClickEvent)
{
GenericClickEvent = genericClickEvent;
Button = button;
}
public VanillaButtonClickEvent GenericClickEvent { get; } = genericClickEvent;
}
/// <summary>
/// Button click event for Vanilla <see cref="AbilityButton"/> only. Do not use for <see cref="CustomActionButton"/>s.
/// </summary>
public sealed class VanillaButtonClickEvent : MiraCancelableEvent
/// <param name="button">The Vanilla <see cref="AbilityButton"/> that was clicked.</param>
/// <param name="playerTarget">The <see cref="PlayerControl"/> target, if any.</param>
/// <param name="ventTarget">The <see cref="Vent"/> target, if any.</param>
public sealed class VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null) : MiraCancelableEvent
{
/// <summary>
/// Gets the Vanilla <see cref="AbilityButton"/> that was clicked.
/// </summary>
public AbilityButton Button { get; }
public AbilityButton Button { get; } = button;
/// <summary>
/// Gets the target <see cref="PlayerControl"/>, if any, of the Vanilla <see cref="AbilityButton"/> that was clicked.
/// </summary>
public PlayerControl? PlayerTarget { get; }
public PlayerControl? PlayerTarget { get; } = playerTarget;
/// <summary>
/// Gets the target <see cref="Vent"/>, if any, of the Vanilla <see cref="AbilityButton"/> that was clicked.
/// </summary>
public Vent? VentTarget { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent"/> class.
/// </summary>
/// <param name="button">The Vanilla <see cref="AbilityButton"/> that was clicked.</param>
/// <param name="playerTarget">The <see cref="PlayerControl"/> target, if any.</param>
/// <param name="ventTarget">The <see cref="Vent"/> target, if any.</param>
public VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null)
{
Button = button;
PlayerTarget = playerTarget;
VentTarget = ventTarget;
}
public Vent? VentTarget { get; } = ventTarget;
}
+3 -11
View File
@@ -3,19 +3,11 @@
/// <summary>
/// Event fired when a player closes the doors in a room.
/// </summary>
public class CloseDoorsEvent : MiraCancelableEvent
/// <param name="room">The room that was closed.</param>
public class CloseDoorsEvent(SystemTypes room) : MiraCancelableEvent
{
/// <summary>
/// Gets the room that the doors were closed in.
/// </summary>
public SystemTypes Room { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CloseDoorsEvent"/> class.
/// </summary>
/// <param name="room">The room that was closed.</param>
public CloseDoorsEvent(SystemTypes room)
{
Room = room;
}
public SystemTypes Room { get; } = room;
}
@@ -3,19 +3,11 @@
/// <summary>
/// Sabotage from Vanilla Among Us.
/// </summary>
public class PlayerOpenSabotageEvent : MiraCancelableEvent
/// <param name="mapBehaviour">The <see cref="global::MapBehaviour"/>.</param>
public class PlayerOpenSabotageEvent(MapBehaviour mapBehaviour) : MiraCancelableEvent
{
/// <summary>
/// Gets the <see cref="global::MapBehaviour"/>.
/// </summary>
public MapBehaviour MapBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerOpenSabotageEvent"/> class.
/// </summary>
/// <param name="mapBehaviour">The <see cref="global::MapBehaviour"/>.</param>
public PlayerOpenSabotageEvent(MapBehaviour mapBehaviour)
{
MapBehaviour = mapBehaviour;
}
public MapBehaviour MapBehaviour { get; } = mapBehaviour;
}
@@ -3,33 +3,23 @@
/// <summary>
/// <see cref="MiraCancelableEvent"/> that is invoked before a system is updated. Usually used for sabotage events.
/// </summary>
public class UpdateSystemEvent : MiraCancelableEvent
/// <param name="systemType">The <see cref="SystemTypes"/> being updated.</param>
/// <param name="player">The <see cref="PlayerControl"/> that is updating the system.</param>
/// <param name="amount">Amount to update System to.</param>
public class UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount) : MiraCancelableEvent
{
/// <summary>
/// Gets the type of system that will be updated.
/// </summary>
public SystemTypes SystemType { get; }
public SystemTypes SystemType { get; } = systemType;
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is updating the system.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the byte amount the system is being updated by.
/// </summary>
public byte Amount { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UpdateSystemEvent"/> class.
/// </summary>
/// <param name="systemType">The <see cref="SystemTypes"/> being updated.</param>
/// <param name="player">The <see cref="PlayerControl"/> that is updating the system.</param>
/// <param name="amount">Amount to update System to.</param>
public UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount)
{
SystemType = systemType;
Player = player;
Amount = amount;
}
public byte Amount { get; } = amount;
}
@@ -3,19 +3,11 @@
/// <summary>
/// The event that is invoked when a player is ejected. Non-cancelable.
/// </summary>
public class EjectionEvent : MiraEvent
/// <param name="controller">The <see cref="global::ExileController"/> instance.</param>
public class EjectionEvent(ExileController controller) : MiraEvent
{
/// <summary>
/// Gets the <see cref="global::ExileController"/> instance.
/// </summary>
public ExileController ExileController { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EjectionEvent"/> class.
/// </summary>
/// <param name="controller">The <see cref="global::ExileController"/> instance.</param>
public EjectionEvent(ExileController controller)
{
ExileController = controller;
}
public ExileController ExileController { get; } = controller;
}
@@ -1,36 +1,26 @@
namespace MiraAPI.Events.Vanilla.Meeting;
/// <summary>
/// The event that is invoked when a body is reported.
/// The event that is invoked when a body is reported.<br/>
/// This is invoked in <see cref="PlayerControl.CmdReportDeadBody(NetworkedPlayerInfo)"/>.
/// </summary>
public class ReportBodyEvent : MiraCancelableEvent
/// <param name="reporter">The player who reported the body.</param>
/// <param name="target">The player being reported.</param>
/// <param name="body">The <see cref="DeadBody"/> being reported.</param>
public class ReportBodyEvent(PlayerControl reporter, NetworkedPlayerInfo? target, DeadBody? body) : MiraCancelableEvent
{
/// <summary>
/// Gets the player that is reporting the body.
/// </summary>
public PlayerControl Reporter { get; }
public PlayerControl Reporter { get; } = reporter;
/// <summary>
/// Gets the player that is being reported. Will be <see langword="null"/> for emergency meeting.
/// </summary>
public NetworkedPlayerInfo? Target { get; }
public NetworkedPlayerInfo? Target { get; } = target;
/// <summary>
/// Gets the <see cref="DeadBody"/> that is being reported. Will be <see langword="null"/> for emergency meeting.
/// </summary>
public DeadBody? Body { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ReportBodyEvent"/> class.
/// </summary>
/// <param name="reporter">The player who reported the body.</param>
/// <param name="target">The player being reported.</param>
/// <param name="body">The <see cref="DeadBody"/> being reported.</param>
public ReportBodyEvent(PlayerControl reporter, NetworkedPlayerInfo? target, DeadBody? body)
{
Reporter = reporter;
Target = target;
Body = body;
}
public DeadBody? Body { get; } = body;
}
@@ -4,19 +4,11 @@
/// The event that is invoked when a meeting is called. This event is not cancelable.
/// This event is called after Mira resets votes, so if you plan on adding votes to a specific player, do it with this event.
/// </summary>
public class StartMeetingEvent : MiraEvent
/// <param name="meetingHud">The <see cref="global::MeetingHud"/> instance.</param>
public class StartMeetingEvent(MeetingHud meetingHud) : MiraEvent
{
/// <summary>
/// Gets the <see cref="global::MeetingHud"/> instance.
/// </summary>
public MeetingHud MeetingHud { get; }
/// <summary>
/// Initializes a new instance of the <see cref="StartMeetingEvent"/> class.
/// </summary>
/// <param name="meetingHud">The <see cref="global::MeetingHud"/> instance.</param>
public StartMeetingEvent(MeetingHud meetingHud)
{
MeetingHud = meetingHud;
}
public MeetingHud MeetingHud { get; } = meetingHud;
}
@@ -3,26 +3,17 @@
/// <summary>
/// Event that is invoked after the local player successfully votes a player or skips. This event is not cancelable.
/// </summary>
public class AfterVoteEvent : MiraEvent
/// <param name="playerVoteArea">The <see cref="PlayerVoteArea"/> that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public class AfterVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter) : MiraEvent
{
/// <summary>
/// Gets the instance of the voter's <see cref="PlayerVoteArea"/>.
/// </summary>
public PlayerVoteArea VoteArea { get; }
public PlayerVoteArea VoteArea { get; } = playerVoteArea;
/// <summary>
/// Gets the player who voted.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="AfterVoteEvent"/> class.
/// </summary>
/// <param name="playerVoteArea">The <see cref="PlayerVoteArea"/> that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public AfterVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
{
VoteArea = playerVoteArea;
Player = voter;
}
public PlayerControl Player { get; } = voter;
}
@@ -3,26 +3,17 @@
/// <summary>
/// Event that is invoked before the local player confirms a vote on a player or when skipping. This event is cancelable.
/// </summary>
public class BeforeVoteEvent : MiraCancelableEvent
/// <param name="playerVoteArea">The <see cref="PlayerVoteArea"/> that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public class BeforeVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter) : MiraCancelableEvent
{
/// <summary>
/// Gets the instance of the voter's <see cref="PlayerVoteArea"/>.
/// </summary>
public PlayerVoteArea VoteArea { get; }
public PlayerVoteArea VoteArea { get; } = playerVoteArea;
/// <summary>
/// Gets the player who voted.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeVoteEvent"/> class.
/// </summary>
/// <param name="playerVoteArea">The <see cref="PlayerVoteArea"/> that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public BeforeVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
{
VoteArea = playerVoteArea;
Player = voter;
}
public PlayerControl Player { get; } = voter;
}
@@ -3,24 +3,16 @@
/// <summary>
/// Checks if voting is complete. If canceled, the default end voting logic will be skipped.
/// </summary>
public class CheckForEndVotingEvent : MiraCancelableEvent
/// <param name="isVotingComplete"><see langword="true"/> if voting is complete, <see langword="false"/> otherwise.</param>
public class CheckForEndVotingEvent(bool isVotingComplete) : MiraCancelableEvent
{
/// <summary>
/// Gets a value indicating whether default voting logic determines that voting is complete.
/// </summary>
public bool IsVotingComplete { get; }
public bool IsVotingComplete { get; } = isVotingComplete;
/// <summary>
/// Gets or sets a value indicating whether the voting should be forced to end, regardless of the default logic.
/// </summary>
public bool ForceEndVoting { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CheckForEndVotingEvent"/> class.
/// </summary>
/// <param name="isVotingComplete"><see langword="true"/> if voting is complete, <see langword="false"/> otherwise.</param>
public CheckForEndVotingEvent(bool isVotingComplete)
{
IsVotingComplete = isVotingComplete;
}
}
@@ -7,38 +7,27 @@ namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Invoked before the dummy selects their vote. If canceled, the dummy won't vote.
/// </summary>
public class DummyVoteEvent : MiraCancelableEvent
/// <param name="dummy">The voting dummy.</param>
public class DummyVoteEvent(DummyBehaviour dummy) : MiraCancelableEvent
{
/// <summary>
/// Gets the dummy that needs to vote.
/// </summary>
public DummyBehaviour Dummy { get; }
public DummyBehaviour Dummy { get; } = dummy;
/// <summary>
/// Gets the instance of the dummy's <see cref="PlayerVoteData"/>.
/// </summary>
public PlayerVoteData VoteData { get; }
public PlayerVoteData VoteData { get; } = dummy.myPlayer.GetVoteData();
/// <summary>
/// Gets or sets a value indicating whether the dummy can vote to skip the meeting.
/// </summary>
public bool CanSkip { get; set; }
public bool CanSkip { get; set; } = true;
/// <summary>
/// Gets or sets a predicate for validating whether the dummy can vote on a player.
/// By default, excludes dead and disconnected players.
/// </summary>
public Predicate<PlayerControl> PlayerIsValid { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DummyVoteEvent"/> class.
/// </summary>
/// <param name="dummy">The voting dummy.</param>
public DummyVoteEvent(DummyBehaviour dummy)
{
Dummy = dummy;
VoteData = dummy.myPlayer.GetVoteData();
CanSkip = true;
PlayerIsValid = x => !x.Data.IsDead && !x.Data.Disconnected;
}
public Predicate<PlayerControl> PlayerIsValid { get; set; } = x => !x.Data.IsDead && !x.Data.Disconnected;
}
@@ -4,47 +4,43 @@ namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// The event that is invoked when Mira handles a vote. This is invoked before Mira's behaviour, so be cautious.
/// If you intend on adding custom vote behaviour, cancel the event and do so.
/// If you are NOT canceling the event, keep in mind that Mira automatically removes a vote and adds the suspect to the <see cref="PlayerVoteData.Votes"/> list after this event is invoked.
/// </summary>
public class HandleVoteEvent : MiraCancelableEvent
/// <remarks>
/// If you intend on adding custom vote behaviour, cancel the event and do so.<br/>
/// If you are NOT canceling the event, keep in mind that Mira automatically removes a vote and adds the suspect to the <see cref="PlayerVoteData.Votes"/> list after this event is invoked.
/// </remarks>
/// <param name="playerVoteData">The voter's <see cref="PlayerVoteData"/>.</param>
/// <param name="targetId">The target's playerId.</param>
/// <param name="isOverruling">Whether the vote is actually for a Judge.</param>
public class HandleVoteEvent(PlayerVoteData playerVoteData, byte targetId, bool isOverruling = false) : MiraCancelableEvent
{
/// <summary>
/// Gets the instance of the voter's <see cref="PlayerVoteData"/>.
/// </summary>
public PlayerVoteData VoteData { get; }
public PlayerVoteData VoteData { get; } = playerVoteData;
/// <summary>
/// Gets the player who voted.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = playerVoteData.Owner;
/// <summary>
/// Gets the player id of the target.
/// </summary>
public byte TargetId { get; }
public byte TargetId { get; } = targetId;
/// <summary>
/// Gets or sets a value indicating whether to prevent the vote from commencing.
/// </summary>
public bool PreventVote { get; set; }
public bool PreventVote { get; set; } = false;
/// <summary>
/// Gets the <see cref="NetworkedPlayerInfo"/> of the target.
/// </summary>
public NetworkedPlayerInfo TargetPlayerInfo { get; }
public NetworkedPlayerInfo TargetPlayerInfo { get; } = GameData.Instance.GetPlayerById(targetId);
/// <summary>
/// Initializes a new instance of the <see cref="HandleVoteEvent"/> class.
/// Gets or sets a value indicating whether the vote event is an Overrule caused by a Judge.
/// </summary>
/// <param name="playerVoteData">The voter's <see cref="PlayerVoteData"/>.</param>
/// <param name="targetId">The target's playerId.</param>
public HandleVoteEvent(PlayerVoteData playerVoteData, byte targetId)
{
VoteData = playerVoteData;
Player = playerVoteData.Owner;
TargetId = targetId;
PreventVote = false;
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
}
public bool IsOverruling { get; set; } = isOverruling;
}
@@ -5,39 +5,28 @@ namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// The event that is invoked when the player tries to select another player to vote.
/// </summary>
public class MeetingSelectEvent : MiraEvent
/// <param name="playerVoteData">The voter's <see cref="PlayerVoteData"/>.</param>
/// <param name="targetId">The target's playerId.</param>
/// <param name="allowSelect">Whether the player is allowed to select the target.</param>
public class MeetingSelectEvent(PlayerVoteData playerVoteData, int targetId, bool allowSelect) : MiraEvent
{
/// <summary>
/// Gets the instance of the voter's <see cref="PlayerVoteData"/>.
/// </summary>
public PlayerVoteData VoteData { get; }
public PlayerVoteData VoteData { get; } = playerVoteData;
/// <summary>
/// Gets the player id of the target.
/// </summary>
public int TargetId { get; }
public int TargetId { get; } = targetId;
/// <summary>
/// Gets the <see cref="NetworkedPlayerInfo"/> of the target.
/// </summary>
public NetworkedPlayerInfo TargetPlayerInfo { get; }
public NetworkedPlayerInfo TargetPlayerInfo { get; } = GameData.Instance.GetPlayerById((byte)targetId);
/// <summary>
/// Gets or sets a value indicating whether the player is allowed to select the target.
/// </summary>
public bool AllowSelect { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MeetingSelectEvent"/> class.
/// </summary>
/// <param name="playerVoteData">The voter's <see cref="PlayerVoteData"/>.</param>
/// <param name="targetId">The target's playerId.</param>
/// <param name="allowSelect">Whether the player is allowed to select the target.</param>
public MeetingSelectEvent(PlayerVoteData playerVoteData, int targetId, bool allowSelect)
{
VoteData = playerVoteData;
TargetId = targetId;
TargetPlayerInfo = GameData.Instance.GetPlayerById((byte)targetId);
AllowSelect = allowSelect;
}
public bool AllowSelect { get; set; } = allowSelect;
}
@@ -5,21 +5,15 @@ namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Called in <see cref="VotingUtils.HandlePopulateResults"/>.
/// Cancelling is NOT advised but if you do plan on cancelling, please ensure you account for displaying ALL player's votes.
/// </summary>
public class PopulateResultsEvent : MiraCancelableEvent
/// <remarks>
/// Cancelling is NOT advised but if you do plan on cancelling, please ensure you account for displaying ALL player's votes.
/// </remarks>
/// <param name="votes">The list of <see cref="CustomVote"/>s.</param>
public class PopulateResultsEvent(List<CustomVote> votes) : MiraCancelableEvent
{
/// <summary>
/// Gets a list of networked <see cref="CustomVote"/>s.
/// </summary>
public List<CustomVote> Votes { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PopulateResultsEvent"/> class.
/// </summary>
/// <param name="votes">The list of <see cref="CustomVote"/>s.</param>
public PopulateResultsEvent(List<CustomVote> votes)
{
Votes = votes;
}
public List<CustomVote> Votes { get; } = votes;
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using MiraAPI.Voting;
@@ -7,16 +8,11 @@ namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Ran after calculating votes and before displaying the results. Only ran on the host.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ProcessVotesEvent"/> class.
/// </remarks>
/// <param name="votes">The list of <see cref="CustomVote"/>s that are being processed.</param>
/// <param name="exiledPlayer">The player to be exiled. Will be <see langword="null"/> if no player is to be exiled.</param>
public class ProcessVotesEvent(List<CustomVote> votes, NetworkedPlayerInfo? exiledPlayer = null) : MiraEvent
{
private readonly List<CustomVote> _originalVotes = [.. votes];
private NetworkedPlayerInfo? _exiledPlayer = exiledPlayer;
private readonly List<CustomVote> originalVotes = [.. votes];
/// <summary>
/// Gets a value indicating whether the exiled player has been modified by the event.
@@ -26,23 +22,35 @@ public class ProcessVotesEvent(List<CustomVote> votes, NetworkedPlayerInfo? exil
/// <summary>
/// Gets a value indicating whether the votes have been modified by the event.
/// </summary>
public bool VotesModified => !Votes.SequenceEqual(_originalVotes);
public bool VotesModified => !Votes.SequenceEqual(originalVotes);
/// <summary>
/// Gets the list of <see cref="CustomVote"/>s that are being processed.
/// </summary>
public List<CustomVote> Votes { get; } = votes;
/// <summary>
/// Gets or sets a value indicating whether an overruling was done. Normally set by judge but can be overwritten.
/// </summary>
public bool OverruledVote { get; set; }
/// <summary>
/// Gets or sets a value specifically to determine which Judge (if any) caused the overruling. Normally set by judge but can be overwritten.
/// </summary>
public ushort OverruledNonce { get; set; }
/// <summary>
/// Gets or sets the player to be exiled. Will be <see langword="null"/> if no player is to be exiled.
/// </summary>
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")]
public NetworkedPlayerInfo? ExiledPlayer
{
get => _exiledPlayer;
get;
set
{
_exiledPlayer = value;
field = value;
ExiledPlayerModified = true;
}
}
= exiledPlayer;
}
@@ -3,33 +3,23 @@
/// <summary>
/// The event that is invoked when a player completes a task within Hide and Seek. Non-cancelable.
/// </summary>
public class CompleteHnsTaskEvent : MiraEvent
/// <param name="hnsManager">The <see cref="HideAndSeekManager"/> currently active.</param>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public class CompleteHnsTaskEvent(HideAndSeekManager hnsManager, PlayerControl player, PlayerTask task) : MiraEvent
{
/// <summary>
/// Gets the instance of the <see cref="PlayerControl"/>.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
/// </summary>
public PlayerTask Task { get; }
public PlayerTask Task { get; } = task;
/// <summary>
/// Gets the instance of the <see cref="HideAndSeekManager"/> attached to the game.
/// </summary>
public HideAndSeekManager HnsManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CompleteHnsTaskEvent"/> class.
/// </summary>
/// <param name="hnsManager">The <see cref="HideAndSeekManager"/> currently active.</param>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public CompleteHnsTaskEvent(HideAndSeekManager hnsManager, PlayerControl player, PlayerTask task)
{
HnsManager = hnsManager;
Player = player;
Task = task;
}
public HideAndSeekManager HnsManager { get; } = hnsManager;
}
@@ -1,28 +1,19 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// The event that is invoked when a player completes a task. Non cancelable.
/// The event that is invoked when a player completes a task. Non-cancelable.
/// </summary>
public class CompleteTaskEvent : MiraEvent
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public class CompleteTaskEvent(PlayerControl player, PlayerTask task) : MiraEvent
{
/// <summary>
/// Gets the instance of the <see cref="PlayerControl"/>.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
/// </summary>
public PlayerTask Task { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CompleteTaskEvent"/> class.
/// </summary>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public CompleteTaskEvent(PlayerControl player, PlayerTask task)
{
Player = player;
Task = task;
}
public PlayerTask Task { get; } = task;
}
@@ -1,35 +1,25 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player dies. Non cancelable.
/// Event that is invoked when a player dies. Non-cancelable.
/// </summary>
public class PlayerDeathEvent : MiraEvent
/// <param name="player">The player who died.</param>
/// <param name="reason">The reason the player died.</param>
/// <param name="deadBody">The player's <see cref="global::DeadBody"/>, if it exists.</param>
public class PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody) : MiraEvent
{
/// <summary>
/// Gets the player who died.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the reason the player died.
/// </summary>
public DeathReason DeathReason { get; }
public DeathReason DeathReason { get; } = reason;
/// <summary>
/// Gets the <see cref="global::DeadBody"/> associated with the player, if any.
/// </summary>
public DeadBody? DeadBody { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerDeathEvent"/> class.
/// </summary>
/// <param name="player">The player who died.</param>
/// <param name="reason">The reason the player died.</param>
/// <param name="deadBody">The player's <see cref="global::DeadBody"/>, if it exists.</param>
public PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody)
{
Player = player;
DeathReason = reason;
DeadBody = deadBody;
}
public DeadBody? DeadBody { get; } = deadBody;
}
@@ -3,21 +3,13 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player joins the lobby. Non cancelable.
/// Event that is invoked when a player joins the lobby. Non-cancelable.
/// </summary>
public class PlayerJoinEvent : MiraEvent
/// <param name="data">The data of the player who joined.</param>
public class PlayerJoinEvent(ClientData data) : MiraEvent
{
/// <summary>
/// Gets the player who joined.
/// </summary>
public ClientData ClientData { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerJoinEvent"/> class.
/// </summary>
/// <param name="data">The data of the player who joined.</param>
public PlayerJoinEvent(ClientData data)
{
ClientData = data;
}
public ClientData ClientData { get; } = data;
}
@@ -3,28 +3,19 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player leaves the game. Non cancelable.
/// Event that is invoked when a player leaves the game. Non-cancelable.
/// </summary>
public class PlayerLeaveEvent : MiraEvent
/// <param name="data">The data of the player who left.</param>
/// <param name="reason">The reason why the player left.</param>
public class PlayerLeaveEvent(ClientData data, DisconnectReasons reason) : MiraEvent
{
/// <summary>
/// Gets the player who left.
/// </summary>
public ClientData ClientData { get; }
public ClientData ClientData { get; } = data;
/// <summary>
/// Gets the reason why the player left.
/// </summary>
public DisconnectReasons Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerLeaveEvent"/> class.
/// </summary>
/// <param name="data">The data of the player who left.</param>
/// <param name="reason">The reason why the player left.</param>
public PlayerLeaveEvent(ClientData data, DisconnectReasons reason)
{
ClientData = data;
Reason = reason;
}
public DisconnectReasons Reason { get; } = reason;
}
@@ -3,26 +3,17 @@
/// <summary>
/// Event that is invoked when a player enters a vent. This event is cancelable.
/// </summary>
public class EnterVentEvent : MiraCancelableEvent
/// <param name="player">The <see cref="PlayerControl"/> that is entering the <paramref name="vent"/>.</param>
/// <param name="vent">The <see cref="global::Vent"/> being entered.</param>
public class EnterVentEvent(PlayerControl player, Vent? vent) : MiraCancelableEvent
{
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is entering the <see cref="Vent"/>.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the <see cref="global::Vent"/> that the <see cref="Player"/> is entering.
/// </summary>
public Vent? Vent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EnterVentEvent"/> class.
/// </summary>
/// <param name="player">The <see cref="PlayerControl"/> that is entering the <paramref name="vent"/>.</param>
/// <param name="vent">The <see cref="global::Vent"/> being entered.</param>
public EnterVentEvent(PlayerControl player, Vent? vent)
{
Player = player;
Vent = vent;
}
public Vent? Vent { get; } = vent;
}
@@ -3,26 +3,17 @@
/// <summary>
/// Event that is invoked when a player exits a vent. This event is cancelable.
/// </summary>
public class ExitVentEvent : MiraCancelableEvent
/// <param name="player">The <see cref="PlayerControl"/> who is exiting the <paramref name="vent"/>.</param>
/// <param name="vent">The <see cref="global::Vent"/> being exited from.</param>
public class ExitVentEvent(PlayerControl player, Vent? vent) : MiraCancelableEvent
{
/// <summary>
/// Gets the <see cref="PlayerControl"/> that is exiting the <see cref="Vent"/>.
/// </summary>
public PlayerControl Player { get; }
public PlayerControl Player { get; } = player;
/// <summary>
/// Gets the <see cref="global::Vent"/> that the <see cref="Player"/> is exiting.
/// </summary>
public Vent? Vent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ExitVentEvent"/> class.
/// </summary>
/// <param name="player">The <see cref="PlayerControl"/> who is exiting the <paramref name="vent"/>.</param>
/// <param name="vent">The <see cref="global::Vent"/> being exited from.</param>
public ExitVentEvent(PlayerControl player, Vent? vent)
{
Player = player;
Vent = vent;
}
public Vent? Vent { get; } = vent;
}
@@ -1,15 +1,7 @@
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event for if a <see cref="PlayerControl"/> can use an <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
/// Event for if a <see cref="PlayerControl"/> can use an <see cref="IUsable"/> from Vanilla Among Us. Will always be run locally.
/// </summary>
public class PlayerCanUseEvent : PlayerUseEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="PlayerCanUseEvent"/> class.
/// </summary>
/// <param name="usable">The <see cref="IUsable"/>.</param>
public PlayerCanUseEvent(IUsable usable) : base(usable)
{
}
}
/// <param name="usable">The <see cref="IUsable"/>.</param>
public class PlayerCanUseEvent(IUsable usable) : PlayerUseEvent(usable);
@@ -1,34 +1,23 @@
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event for <see cref="PlayerControl"/> using a <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
/// Event for <see cref="PlayerControl"/> using a <see cref="IUsable"/> from Vanilla Among Us. Will always be run locally.
/// </summary>
public class PlayerUseEvent : MiraCancelableEvent
/// <param name="usable">The <see cref="IUsable"/>.</param>
public class PlayerUseEvent(IUsable usable) : MiraCancelableEvent
{
/// <summary>
/// Gets the instance of <see cref="IUsable"/> that was used.
/// </summary>
public IUsable Usable { get; }
public IUsable Usable { get; } = usable;
/// <summary>
/// Gets a value indicating whether the <see cref="Usable"/> is a <see cref="Console"/>, <see cref="MapConsole"/>, or <see cref="SystemConsole"/>.
/// </summary>
public bool IsPrimaryConsole { get; }
public bool IsPrimaryConsole { get; } = usable.TryCast<Console>() || usable.TryCast<SystemConsole>() || usable.TryCast<MapConsole>();
/// <summary>
/// Gets a value indicating whether the <see cref="Usable"/> is a <see cref="Vent"/>.
/// </summary>
public bool IsVent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerUseEvent"/> class.
/// </summary>
/// <param name="usable">The <see cref="IUsable"/>.</param>
public PlayerUseEvent(IUsable usable)
{
Usable = usable;
IsPrimaryConsole = usable.TryCast<Console>() || usable.TryCast<SystemConsole>() || usable.TryCast<MapConsole>();
IsVent = usable.TryCast<Vent>();
}
public bool IsVent { get; } = usable.TryCast<Vent>();
}
+4 -2
View File
@@ -48,7 +48,8 @@ public abstract class CustomGameOver
/// </summary>
/// <typeparam name="T">Type of the custom game over.</typeparam>
/// <returns>The <see cref="global::GameOverReason"/> associated with the custom game over.</returns>
public static GameOverReason GameOverReason<T>() where T : CustomGameOver
public static GameOverReason GameOverReason<T>()
where T : CustomGameOver
{
return (GameOverReason)GameOverManager.GetGameOverId<T>();
}
@@ -58,7 +59,8 @@ public abstract class CustomGameOver
/// </summary>
/// <param name="winners">A collection of winners.</param>
/// <typeparam name="T">Type of the custom game over.</typeparam>
public static void Trigger<T>(IEnumerable<NetworkedPlayerInfo> winners) where T : CustomGameOver
public static void Trigger<T>(IEnumerable<NetworkedPlayerInfo> winners)
where T : CustomGameOver
{
var reason = GameOverManager.GetGameOverId<T>();
var data = new GameOverData(reason, [.. winners]);
+9 -9
View File
@@ -12,7 +12,7 @@ public static class GameOverManager
private static readonly Dictionary<Type, int> GameOverIds = [];
private static readonly Dictionary<int, Type> GameOverTypes = [];
private static int _nextId = Enum.GetNames<GameOverReason>().Length;
private static int nextId = Enum.GetNames<GameOverReason>().Length;
/// <summary>
/// Register a <see cref="CustomGameOver"/>.
@@ -39,9 +39,9 @@ public static class GameOverManager
return false;
}
GameOverIds.Add(gameOverType, _nextId);
GameOverTypes.Add(_nextId, gameOverType);
_nextId++;
GameOverIds.Add(gameOverType, nextId);
GameOverTypes.Add(nextId, gameOverType);
nextId++;
return true;
}
@@ -68,7 +68,8 @@ public static class GameOverManager
/// </summary>
/// <typeparam name="T">Type of the <see cref="CustomGameOver"/>.</typeparam>
/// <returns>The ID of the <see cref="CustomGameOver"/>.</returns>
public static int GetGameOverId<T>() where T : CustomGameOver
public static int GetGameOverId<T>()
where T : CustomGameOver
{
return GetGameOverId(typeof(T));
}
@@ -81,9 +82,8 @@ public static class GameOverManager
/// <exception cref="ArgumentException">Thrown when the type is not registered.</exception>
public static int GetGameOverId(Type gameOverType)
{
if (GameOverIds.TryGetValue(gameOverType, out var id))
return id;
throw new ArgumentException($"{gameOverType.FullName} is not a registered custom game over!");
return GameOverIds.TryGetValue(gameOverType, out var id)
? id
: throw new ArgumentException($"{gameOverType.FullName} is not a registered custom game over!");
}
}
-2
View File
@@ -1,2 +0,0 @@
[*.cs]
dotnet_diagnostic.CS1591.severity = none
+60 -34
View File
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using AmongUs.GameOptions;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using MiraAPI.Translation;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
using TMPro;
@@ -21,13 +22,18 @@ public abstract class AbstractGameMode : IOptionable
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets a value indicating whether the mode is visible in the menu. Only checked at startup.
/// </summary>
public virtual bool HideMode => false;
/// <summary>
/// Gets the game mode description.
/// </summary>
public abstract string Description { get; }
/// <summary>
/// Gets the game mode description.
/// Gets the game mode color to display on the lobby pane.
/// </summary>
public virtual Color Color { get; } = Color.white;
@@ -37,14 +43,14 @@ public abstract class AbstractGameMode : IOptionable
public virtual LoadableAsset<Sprite>? Icon => null;
/// <summary>
/// Gets the icon of the gamemode for use on the options pop-up.
/// Gets or sets the icon of the gamemode for use on the options pop-up.
/// </summary>
public TMP_SpriteAsset TmpIcon { get; set; } = null!;
/// <summary>
/// Gets the colored game mode name, using the color and name properties.
/// </summary>
public string ColoredName => $"<color=#{this.Color.ToHtmlStringRGBA()}>{this.Name}</color>";
public string ColoredName => $"<color=#{Color.ToHtmlStringRGBA()}>{MiraLocaleManager.Get(Name)}</color>";
/// <summary>
/// Gets the game mode id.
@@ -78,15 +84,11 @@ public abstract class AbstractGameMode : IOptionable
/// <returns>Resulting body type.</returns>
public virtual PlayerBodyTypes GetBodyType(PlayerControl player)
{
if (AprilFoolsMode.ShouldHorseAround())
{
return PlayerBodyTypes.Horse;
}
if (AprilFoolsMode.ShouldLongAround())
{
return PlayerBodyTypes.Long;
}
return PlayerBodyTypes.Normal;
return AprilFoolsMode.ShouldHorseAround()
? PlayerBodyTypes.Horse
: (AprilFoolsMode.ShouldLongAround()
? PlayerBodyTypes.Long
: PlayerBodyTypes.Normal);
}
/// <summary>
@@ -171,14 +173,17 @@ public abstract class AbstractGameMode : IOptionable
/// Custom winner selection.
/// </summary>
/// <returns>List of winners or null.</returns>
public virtual List<NetworkedPlayerInfo>? CalculateWinners() => null;
public virtual List<NetworkedPlayerInfo>? CalculateWinners()
{
return null;
}
/// <summary>
/// The IEnumerator that plays the intro cutscene for this gamemode.
/// </summary>
/// <param name="__instance">An instance of IntroCutscene.</param>
/// <param name="introCutscene">An instance of IntroCutscene.</param>
/// <returns>An IEnumerator to run the intro cutscene instead of the base game one.</returns>
public virtual IEnumerator IntroCutscene(IntroCutscene __instance)
public virtual IEnumerator IntroCutscene(IntroCutscene introCutscene)
{
yield return new WaitForEndOfFrame();
}
@@ -188,35 +193,50 @@ public abstract class AbstractGameMode : IOptionable
/// </summary>
/// <param name="console">Admin Console.</param>
/// <returns>True if Admin console is enabled.</returns>
public virtual bool CanUseMapConsole(MapConsole console) => true;
public virtual bool CanUseMapConsole(MapConsole console)
{
return true;
}
/// <summary>
/// Can a body be reported in this gamemode.
/// </summary>
/// <param name="body">Target body for reporting.</param>
/// <returns>True if dead bodies can be reported.</returns>
public virtual bool CanReport(DeadBody body) => true;
public virtual bool CanReport(DeadBody body)
{
return true;
}
/// <summary>
/// Can system consoles be used in this gamemode.
/// </summary>
/// <param name="console">System Console.</param>
/// <returns>True if system consoles are enabled in this mode.</returns>
public virtual bool CanUseSystemConsole(SystemConsole console) => true;
public virtual bool CanUseSystemConsole(SystemConsole console)
{
return true;
}
/// <summary>
/// Can tasks be interacted with in this gamemode.
/// </summary>
/// <param name="console">Task console.</param>
/// <returns>True if tasks are enabled in this mode.</returns>
public virtual bool CanUseTasks(Console console) => true;
public virtual bool CanUseTasks(Console console)
{
return true;
}
/// <summary>
/// Should the sabotage map be used when attempting to open sabotage overlay.
/// </summary>
/// <param name="map">MapBehaviour object.</param>
/// <returns>True if the sabotage map should be shown.</returns>
public virtual bool ShouldShowSabotageMap(MapBehaviour map) => true;
public virtual bool ShouldShowSabotageMap(MapBehaviour map)
{
return true;
}
/// <summary>
/// Gets the <see cref="MapOptions"/> to display on the map.
@@ -238,28 +258,36 @@ public abstract class AbstractGameMode : IOptionable
/// <param name="vent">Target vent.</param>
/// <param name="playerInfo">Player attempting to vent.</param>
/// <returns>True if venting is enabled in this mode.</returns>
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo)
{
return true;
}
/// <summary>
/// Does the task bar appear in the gamemode.
/// Gets a value indicating whether the task bar appears in the gamemode.
/// </summary>
/// <returns>True if the task bar is enabled in this mode.</returns>
public virtual bool ShowTaskBar => true;
/// <summary>
/// Updates the task panel.
/// </summary>
/// <param name="instance">The task panel to update.</param>
public virtual void UpdateTaskPanel(TaskPanelBehaviour instance)
{
instance.background.transform.localScale = (instance.taskText.textBounds.size.x > 0f)
instance.background.transform.localScale = instance.taskText.textBounds.size.x > 0f
? new Vector3(instance.taskText.textBounds.size.x + 0.2f, instance.taskText.textBounds.size.y + 0.2f, 1f)
: Vector3.zero;
Vector3 vector = instance.background.sprite.bounds.extents;
var vector = instance.background.sprite.bounds.extents;
vector.y = -vector.y;
vector = vector.Mul(instance.background.transform.localScale);
instance.background.transform.localPosition = vector;
Vector3 vector2 = instance.tab.sprite.bounds.extents;
var vector2 = instance.tab.sprite.bounds.extents;
vector2 = vector2.Mul(instance.tab.transform.localScale);
vector2.y = -vector2.y;
vector2.x += vector.x * 2f;
instance.tab.transform.localPosition = vector2;
if (GameManager.Instance == null)
{
return;
@@ -269,14 +297,9 @@ public abstract class AbstractGameMode : IOptionable
var xPos = -instance.background.sprite.bounds.size.x * instance.background.transform.localScale.x;
instance.closedPosition = new Vector3(xPos, yPos, instance.closedPosition.z);
instance.openPosition = new Vector3(instance.openPosition.x, yPos, instance.openPosition.z);
if (instance.open)
{
instance.timer = Mathf.Min(1f, instance.timer + Time.deltaTime / instance.animationTimeSeconds);
}
else
{
instance.timer = Mathf.Max(0f, instance.timer - Time.deltaTime / instance.animationTimeSeconds);
}
instance.timer = instance.open
? Mathf.Min(1f, instance.timer + (Time.deltaTime / instance.animationTimeSeconds))
: Mathf.Max(0f, instance.timer - (Time.deltaTime / instance.animationTimeSeconds));
Vector3 relativePos = new(
Mathf.SmoothStep(instance.closedPosition.x, instance.openPosition.x, instance.timer),
@@ -287,5 +310,8 @@ public abstract class AbstractGameMode : IOptionable
}
/// <inheritdoc/>
public override string ToString() => Name;
public override string ToString()
{
return Name;
}
}
+4 -3
View File
@@ -11,10 +11,11 @@ namespace MiraAPI.GameModes;
public class ClassicMode : AbstractGameMode
{
/// <inheritdoc/>
public override string Name => "Classic";
public override string Name => "MiraApi.Gamemode.Classic";
/// <inheritdoc/>
public override string Description => "The classic Among Us experience!\nFind the Impostors or deceive the Crew.";
public override string Description => "MiraApi.Gamemode.Classic.Description";
public override LoadableAsset<Sprite>? Icon => MiraAssets.ClassicGamemodeIcon;
/// <inheritdoc/>
public override LoadableAsset<Sprite> Icon => MiraAssets.ClassicGamemodeIcon;
}
+36 -18
View File
@@ -2,8 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
namespace MiraAPI.GameModes;
@@ -14,7 +12,10 @@ public static class CustomGameModeManager
{
internal static readonly Dictionary<uint, AbstractGameMode> IdToModeMap = [];
private static uint GetNextId() => ++LastId;
private static uint GetNextId()
{
return ++LastId;
}
private static uint LastId { get; set; }
@@ -40,6 +41,9 @@ public static class CustomGameModeManager
return false;
}
if (mode.HideMode)
return true;
IdToModeMap.Add(GetNextId(), mode);
pluginInfo.GameModes.Add(LastId, mode);
mode.ID = LastId;
@@ -51,20 +55,30 @@ public static class CustomGameModeManager
/// Checks to see if the current game mode is an instance of the classic mode.
/// </summary>
/// <returns>True if the classic mode is the current one.</returns>
public static bool IsClassic() => ActiveMode != null && ActiveMode.GetType().IsAssignableTo(typeof(ClassicMode));
public static bool IsClassic()
{
return ActiveMode != null && ActiveMode.GetType().IsAssignableTo(typeof(ClassicMode));
}
/// <summary>
/// Checks to see if the current game mode is an instance of the HNS mode.
/// </summary>
/// <returns>True if the Hide & Seek mode is the current one.</returns>
public static bool IsHideNSeek() => ActiveMode != null && ActiveMode.GetType().IsAssignableTo(typeof(HideAndSeekMode));
/// <returns>True if the Hide n Seek mode is the current one.</returns>
public static bool IsHideNSeek()
{
return ActiveMode != null && ActiveMode.GetType().IsAssignableTo(typeof(HideAndSeekMode));
}
/// <summary>
/// Checks if a provided GameMode is the current active one.
/// </summary>
/// <typeparam name="T">The AbstractGameMode subclass being checked.</typeparam>
/// <returns>Whether the provided mode is the current active one.</returns>
public static bool IsActiveGameMode<T>() where T : AbstractGameMode => ActiveMode is T;
public static bool IsActiveGameMode<T>()
where T : AbstractGameMode
{
return ActiveMode is T;
}
/// <summary>
/// Gets the current gamemode.
@@ -76,7 +90,10 @@ public static class CustomGameModeManager
/// </summary>
/// <param name="id">The ID of the gamemode to fetch.</param>
/// <returns>The gamemode matching that ID.</returns>
public static AbstractGameMode GetMode(uint id) => IdToModeMap[id];
public static AbstractGameMode GetMode(uint id)
{
return IdToModeMap[id];
}
/// <summary>
/// Finds the parent mod of a custom gamemode.
@@ -92,18 +109,19 @@ public static class CustomGameModeManager
{
var defaultMode = new ClassicMode();
IdToModeMap.Add(0, defaultMode);
// no need to add to game mode option as it already contains it
// because we cannot have the option be created with no values
defaultMode.ID = 0;
// TODO: Once Hide n Seek port is ready for release, we will remove this case.
if (MiraApiPlugin.IsDevBuild)
{
var hnsMode = new HideAndSeekMode();
IdToModeMap.Add(1, hnsMode);
hnsMode.ID = 1;
GameModeOption.AddOption(hnsMode);
LastId++;
}
var hnsMode = new HideAndSeekMode();
if (hnsMode.HideMode)
return;
IdToModeMap.Add(1, hnsMode);
hnsMode.ID = 1;
GameModeOption.AddOption(hnsMode);
LastId++;
}
internal static void GetAndSetGameMode()
@@ -117,6 +135,6 @@ public static class CustomGameModeManager
}
ActiveMode = IdToModeMap[0];
Logger<MiraApiPlugin>.Warning($"Unable to find game mode of id {id}!");
Warning($"Unable to find game mode of id {id}!");
}
}
+53 -42
View File
@@ -1,21 +1,15 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.GameOptions;
using MiraAPI.Networking;
using MiraAPI.Patches;
using MiraAPI.Patches.GameModes;
using MiraAPI.Patches.Options;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Localization.Utilities;
using MiraAPI.Translation;
using Reactor.Networking.Attributes;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.ProBuilder;
using Object = UnityEngine.Object;
namespace MiraAPI.GameModes;
@@ -26,7 +20,7 @@ namespace MiraAPI.GameModes;
public static class GameModeOption
{
/// <summary>
/// Gets the current index of the Game Mode Option
/// Gets the current index of the Game Mode Option.<br/>
/// For the value as an <see cref="AbstractGameMode"/>, see <see cref="CustomGameModeManager.ActiveMode"/>.
/// </summary>
public static int Value
@@ -34,32 +28,37 @@ public static class GameModeOption
get =>
OptionBehaviour != null
? OptionBehaviour.GetInt()
: _lastValue;
: LastValue;
private set
{
_lastValue = value;
LastValue = value;
if (OptionBehaviour == null)
return;
OptionBehaviour.Value = value;
OptionBehaviour.UpdateValue();
_lastValue = value;
LastValue = value;
}
}
internal static StringOption OptionBehaviour { get; set; } = null!;
internal static int _lastValue;
internal static readonly StringNames GamemodeName = CustomStringName.CreateAndRegister("Gamemode");
internal static readonly StringNames CustomName = CustomStringName.CreateAndRegister("Custom");
internal static readonly StringNames GamemodeName = MiraLocaleManager.GetOrCreateLocaleString("MiraApi.Gamemode");
internal static readonly StringNames CustomName = MiraLocaleManager.GetOrCreateLocaleString("Custom");
internal static readonly Dictionary<uint, StringNames> Values = new()
{
[0] = CustomStringName.CreateAndRegister("Classic"),
[0] = MiraLocaleManager.GetOrCreateLocaleString("MiraApi.Gamemode.Classic"),
};
internal static StringOption OptionBehaviour { get; set; } = null!;
[SuppressMessage("Critical Code Smell", "S2223:Non-constant static fields should not be visible", Justification = "Internal behaviour that does not need property-level validation.")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")] // why so many warnings???
internal static int LastValue;
internal static void AddOption(AbstractGameMode mode)
{
if (!Values.ContainsKey(mode.ID))
Values.Add(mode.ID, CustomStringName.CreateAndRegister(mode.ColoredName));
Values.Add(mode.ID, MiraLocaleManager.GetOrCreateLocaleString(mode.Name));
}
/*[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))]
[HarmonyPostfix]
private static void CreateSettingsPatch(GameOptionsMenu __instance)
@@ -109,38 +108,50 @@ public static class GameModeOption
internal static void Set(int val)
{
Value = val;
var previousMode = CustomGameModeManager.ActiveMode;
CustomGameModeManager.GetAndSetGameMode();
HudPatches.SetGameModeText(CustomGameModeManager.GetMode(Values.ElementAt(_lastValue).Key).ColoredName);
HudPatches.SetGameModeText(CustomGameModeManager.GetMode(Values.ElementAt(LastValue).Key).ColoredName);
var gm = CustomGameModeManager.ActiveMode!;
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
GamemodeName,
gm.ColoredName,
new Color(0.7333f, 0.7333f, 0.7333f, 1),
gm.TmpIcon,
true);
// could make Values a dict of AbstractGameMode too
if (gm != previousMode)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
GamemodeName,
gm.ColoredName,
new Color(0.7333f, 0.7333f, 0.7333f, 1),
gm.TmpIcon);
if (MenuState.Instance)
{
// should force reset roles for gamemodes properly
foreach (var roleMenu in MenuState.Instance.QueuedRoleMenuRefresh)
{
MenuState.Instance.QueuedRoleMenuRefresh[roleMenu.Key] = true;
}
}
GameOptionsMenuPatch.RefreshForGameMode();
RoleGuidePatches.RefreshSettingsForGameMode();
}
// TODO: could make Values a dict of AbstractGameMode too
}
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.ValueChanged))]
[HarmonyPrefix]
private static bool ValueChanged(GameOptionsMenu __instance, OptionBehaviour option)
private static bool ValueChanged(OptionBehaviour option)
{
if (OptionBehaviour.Equals(option))
{
Info($"Game mode changed to {option.GetInt()}");
RpcSyncGamemode(PlayerControl.LocalPlayer, option.GetInt());
if (GameSettingMenu.Instance && CustomGameModeManager.ActiveMode != null)
{
GameOptionsMenuPatch.ToggleGamemodeOptions(CustomGameModeManager.ActiveMode, __instance);
GameSettingMenu.Instance.RoleSettingsButton.gameObject.SetActive(CustomGameModeManager.ActiveMode.ShowNormalRoleSettings);
}
return false;
}
return true;
if (!OptionBehaviour.Equals(option))
return true;
Info($"Game mode changed to {option.GetInt()}");
RpcSyncGamemode(PlayerControl.LocalPlayer, option.GetInt());
return false;
}
[MethodRpc((uint) MiraRpc.SyncGamemodeOption)]
[MethodRpc((uint)MiraRpc.SyncGamemodeOption)]
[SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Intentionally unused because the main intent is for it to be used by dependent mods to add to a blacklist.")]
internal static void RpcSyncGamemode(PlayerControl host, int data)
{
Set(data);
+246 -235
View File
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AmongUs.Data;
using AmongUs.GameOptions;
@@ -25,278 +26,292 @@ namespace MiraAPI.GameModes;
public class HideAndSeekMode : AbstractGameMode
{
/// <inheritdoc/>
public override string Name => "Hide n Seek";
public override string Name => "MiraApi.Gamemode.HideAndSeek";
/// <inheritdoc/>
public override string Description => "You can run, but you can't hide!\nOutlive the timer or eliminate the crew.";
public override string Description => "MiraApi.Gamemode.HideAndSeek.Description";
/// <inheritdoc/>
// TODO: Once the Hide n Seek port is ready for release, we will remove this.
public override bool HideMode => !MiraApiPlugin.IsDevBuild;
/// <inheritdoc/>
public override Color Color { get; } = new Color32(255, 88, 90, 255);
public override LoadableAsset<Sprite>? Icon => MiraAssets.HnSGamemodeIcon;
public override bool CanReport(DeadBody body) => false;
public override bool ShouldShowSabotageMap(MapBehaviour map) => false;
/// <inheritdoc/>
public override LoadableAsset<Sprite> Icon => MiraAssets.HnSGamemodeIcon;
/// <inheritdoc/>
public override bool ShowGameModeIntroCutscene => true;
/// <inheritdoc/>
public override bool GameModeBodyTypeOverride => true;
/// <inheritdoc/>
public override bool ShowNormalGameSettings => false;
/// <inheritdoc/>
public override bool ShowNormalRoleSettings => false;
/// <inheritdoc/>
public override float DefaultImpostorKillCooldown => 1f;
/// <inheritdoc/>
public override bool ShowTaskBar => false;
public static int ImpostorPlayerID()
/// <inheritdoc/>
public override bool CanReport(DeadBody body)
{
return false;
}
/// <inheritdoc/>
public override bool ShouldShowSabotageMap(MapBehaviour map)
{
return false;
}
private static int ImpostorPlayerID()
{
return OptionGroupSingleton<HnsImpostorOptions>.Instance.SelectedSeeker.Value;
}
public static bool HasImpostorPlayerID()
private static bool HasImpostorPlayerID()
{
return ImpostorPlayerID() > -1;
}
public static bool ValidateImpostorPlayerID(List<NetworkedPlayerInfo> players)
private static bool ValidateImpostorPlayerID(List<NetworkedPlayerInfo> players)
{
return HasImpostorPlayerID() && players.Find((NetworkedPlayerInfo p) => (int)p.PlayerId == ImpostorPlayerID()) != null;
return HasImpostorPlayerID() && players.Find(p => p.PlayerId == ImpostorPlayerID()) != null;
}
/// <inheritdoc/>
public override void AssignRoles(out bool runOriginal, LogicRoleSelectionNormal instance)
{
runOriginal = false;
Il2CppSystem.Collections.Generic.List<ClientData> list = new();
CppCollections.List<ClientData> list = new();
AmongUsClient.Instance.GetAllClients(list);
List<NetworkedPlayerInfo> list2 = list.ToArray()
List<NetworkedPlayerInfo> list2 = [.. list.ToArray()
.Where(c => c.Character != null && c.Character.Data != null && !c.Character.Data.Disconnected &&
!c.Character.Data.IsDead).OrderBy(c => c.Id).Select(c => c.Character.Data)
.ToList();
!c.Character.Data.IsDead).OrderBy(c => c.Id).Select(c => c.Character.Data)];
foreach (NetworkedPlayerInfo networkedPlayerInfo in GameData.Instance.AllPlayers)
foreach (var networkedPlayerInfo in GameData.Instance.AllPlayers)
{
if (networkedPlayerInfo.Object != null && networkedPlayerInfo.Object.isDummy)
{
list2.Add(networkedPlayerInfo);
}
}
IGameOptions currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
int adjustedNumImpostors = GameOptionsManager.Instance.CurrentGameOptions.GetAdjustedNumImpostors(list2.Count);
var currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
var adjustedNumImpostors = GameOptionsManager.Instance.CurrentGameOptions.GetAdjustedNumImpostors(list2.Count);
AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Impostor, Math.Max(adjustedNumImpostors, 1), RoleTypes.Impostor);
AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Crewmate, int.MaxValue, RoleTypes.Engineer);
}
public static void AssignRolesForTeam(
private static void AddGuaranteedRoles(IEnumerable<RoleBehaviour> guaranteedRoles, IGameOptions opts, List<RoleTypes> list)
{
var roleOptions = opts.RoleOptions;
foreach (var roleAssignmentData in guaranteedRoles.Select(x =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
while (true)
{
var count = roleAssignmentData.Count;
roleAssignmentData.Count = count - 1;
if (count <= 0) break;
list.Add(roleAssignmentData.Role.Role);
}
}
}
private static void AddPotentialRoles(IEnumerable<RoleBehaviour> source, IGameOptions opts, List<RoleTypes> list)
{
var roleOptions = opts.RoleOptions;
var potentialRoles = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (var roleData in potentialRoles)
{
for (var i = 0; i < roleData.Count; i++)
{
if (HashRandom.Next(101) < roleData.Chance)
{
list.Add(roleData.Role.Role);
}
}
}
}
private static void AddFallbackRoles(List<RoleTypes> list, int targetPlayerCount, int teamMax, int rolesAssigned, RoleTypes basicRole)
{
while (list.Count < targetPlayerCount && list.Count + rolesAssigned < teamMax)
{
list.Add(basicRole);
}
}
private static void AssignRolesForTeam(
List<NetworkedPlayerInfo> players,
IGameOptions opts,
RoleTeamTypes team,
int teamMax,
RoleTypes defaultRole)
{
Error($"Hide And Seek Mode - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
int num = 0;
IRoleOptionsCollection roleOptions = opts.RoleOptions;
Info($"Hide And Seek Mode - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
var num = 0;
var roleOptions = opts.RoleOptions;
var source = RoleManager.Instance.AllRoles.ToArray()
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
CustomRoleUtils.CanSpawnOnCurrentMode(role));
CustomRoleUtils.CanSpawnOnCurrentMode(role))
.ToList();
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
List<RoleTypes> list = new List<RoleTypes>();
if (team == RoleTeamTypes.Crewmate)
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100))).ToList();
List<RoleTypes> list = [];
switch (team)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
while (true)
case RoleTeamTypes.Crewmate:
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
AddGuaranteedRoles(guaranteedRoles, opts, list);
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
AddPotentialRoles(source, opts, list);
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Potential Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
const RoleTypes basicCrewRole = RoleTypes.Engineer;
AddFallbackRoles(list, players.Count, teamMax, num, basicCrewRole);
AssignRolesFromList(players, teamMax, list, ref num);
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Fallback Assignment");
break;
case RoleTeamTypes.Impostor:
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
var newImpostors = new List<NetworkedPlayerInfo>();
if (HasImpostorPlayerID() &&
ValidateImpostorPlayerID(players) &&
!AmongUsClient.Instance.IsGamePublic)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Potential Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var basicRole = RoleTypes.Engineer;
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
AssignRolesFromList(players, teamMax, list, ref num);
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Fallback Assignment");
}
else if (team == RoleTeamTypes.Impostor)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
var newImpostors = new List<NetworkedPlayerInfo>();
// Specified Seeker
if (HasImpostorPlayerID() &&
ValidateImpostorPlayerID(players) &&
!AmongUsClient.Instance.IsGamePublic)
{
NetworkedPlayerInfo networkedPlayerInfo = players.ToArray()
.First(p => p.PlayerId == ImpostorPlayerID());
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
}
// Random Seeker
else
{
int num2 = 0;
while (num2 < teamMax && players.Count > 0)
{
PseudoRandomList<NetworkedPlayerInfo> pseudoRandomList = new PseudoRandomList<NetworkedPlayerInfo>(AmongUsClient.Instance.GameId);
players.Do(x => pseudoRandomList.Add(x));
for (int i = 0; i < GameData.RoundsPlayedInSession; i++)
{
pseudoRandomList.PickRandom();
}
NetworkedPlayerInfo networkedPlayerInfo = pseudoRandomList.PickRandom();
// Specified Seeker
var networkedPlayerInfo = players.First(p => p.PlayerId == ImpostorPlayerID());
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
num2++;
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
while (true)
else
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
// Random Seeker
var num2 = 0;
while (num2 < teamMax && players.Count > 0)
{
break;
}
PseudoRandomList<NetworkedPlayerInfo> pseudoRandomList = new(AmongUsClient.Instance.GameId);
players.Do(pseudoRandomList.Add);
for (var i = 0; i < GameData.RoundsPlayedInSession; i++)
{
pseudoRandomList.PickRandom();
}
list.Add(roleAssignmentData.Role.Role);
}
}
AssignRolesFromList(newImpostors, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
var networkedPlayerInfo = pseudoRandomList.PickRandom();
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
num2++;
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Seeker is {networkedPlayerInfo.PlayerName}, ID: {networkedPlayerInfo.PlayerId}");
}
}
}
AssignRolesFromList(newImpostors, teamMax, list, ref num);
var basicRole = RoleTypes.Impostor;
while (list.Count < newImpostors.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
Info($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
AssignRolesFromList(newImpostors, teamMax, list, ref num);
AddGuaranteedRoles(guaranteedRoles, opts, list);
AssignRolesFromList(newImpostors, teamMax, list, ref num);
AddPotentialRoles(source, opts, list);
AssignRolesFromList(newImpostors, teamMax, list, ref num);
const RoleTypes basicImpRole = RoleTypes.Impostor;
AddFallbackRoles(list, newImpostors.Count, teamMax, num, basicImpRole);
AssignRolesFromList(newImpostors, teamMax, list, ref num);
break;
default:
throw new ArgumentOutOfRangeException(nameof(team), team, null);
}
}
public static void AssignRolesFromList(List<NetworkedPlayerInfo> players, int teamMax, List<RoleTypes> roleList, ref int rolesAssigned)
private static void AssignRolesFromList(List<NetworkedPlayerInfo> players, int teamMax, List<RoleTypes> roleList, ref int rolesAssigned)
{
while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax)
{
int index = HashRandom.FastNext(roleList.Count);
RoleTypes roleType = roleList[index];
var index = HashRandom.FastNext(roleList.Count);
var roleType = roleList[index];
roleList.RemoveAt(index);
int index2 = HashRandom.FastNext(players.Count);
players[index2].Object.RpcSetRole(roleType, false);
var index2 = HashRandom.FastNext(players.Count);
players[index2].Object.RpcSetRole(roleType);
players.RemoveAt(index2);
rolesAssigned++;
}
}
public override IEnumerator IntroCutscene(IntroCutscene __instance)
/// <inheritdoc/>
public override IEnumerator IntroCutscene(IntroCutscene introCutscene)
{
SoundManager.Instance.PlaySound(__instance.IntroStinger, false, 1f, null);
Logger.GlobalInstance.Info("IntroCutscene :: CoBegin() :: Game Mode: Hide and Seek (MiraAPI)", null);
__instance.LogPlayerRoleData();
__instance.HideAndSeekPanels.SetActive(true);
SoundManager.Instance.PlaySound(introCutscene.IntroStinger, false);
Logger.GlobalInstance.Info("IntroCutscene :: CoBegin() :: Game Mode: Hide and Seek (MiraAPI)");
introCutscene.LogPlayerRoleData();
introCutscene.HideAndSeekPanels.SetActive(true);
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor)
{
__instance.CrewmateRules.SetActive(false);
__instance.ImpostorRules.SetActive(true);
introCutscene.CrewmateRules.SetActive(false);
introCutscene.ImpostorRules.SetActive(true);
}
else
{
__instance.CrewmateRules.SetActive(true);
__instance.ImpostorRules.SetActive(false);
introCutscene.CrewmateRules.SetActive(true);
introCutscene.ImpostorRules.SetActive(false);
}
__instance.ImpostorName.gameObject.SetActive(true);
__instance.ImpostorTitle.gameObject.SetActive(true);
__instance.BackgroundBar.enabled = false;
__instance.TeamTitle.gameObject.SetActive(false);
introCutscene.ImpostorName.gameObject.SetActive(true);
introCutscene.ImpostorTitle.gameObject.SetActive(true);
introCutscene.BackgroundBar.enabled = false;
introCutscene.TeamTitle.gameObject.SetActive(false);
var impostor = PlayerControl.AllPlayerControls.ToArray().FirstOrDefault(x => x.Data.Role.IsImpostor);
if (impostor == null)
{
Logger.GlobalInstance.Error("IntroCutscene :: CoBegin() :: impostor is NULL", null);
Logger.GlobalInstance.Error("IntroCutscene :: CoBegin() :: impostor is NULL");
}
GameManager.Instance.SetSpecialCosmetics(impostor);
if (impostor != null)
{
__instance.ImpostorName.text = impostor.Data.PlayerName;
}
else
{
__instance.ImpostorName.text = "???";
}
introCutscene.ImpostorName.text = impostor != null ? impostor.Data.PlayerName : "???";
yield return new WaitForSecondsRealtime(0.1f);
if (impostor != null)
{
__instance.ImpostorTitle.text = impostor.Data.Role.GetRoleName();
introCutscene.ImpostorTitle.text = impostor.Data.Role.GetRoleName();
}
PoolablePlayer playerSlot = null;
PoolablePlayer? playerSlot = null;
if (impostor != null)
{
playerSlot = __instance.CreatePlayer(1, 1, impostor.Data, false);
playerSlot = introCutscene.CreatePlayer(1, 1, impostor.Data, false);
playerSlot.SetBodyType(PlayerBodyTypes.Normal);
playerSlot.SetFlipX(false);
playerSlot.transform.localPosition = __instance.impostorPos;
playerSlot.transform.localScale = Vector3.one * __instance.impostorScale;
playerSlot.transform.localPosition = introCutscene.impostorPos;
playerSlot.transform.localScale = Vector3.one * introCutscene.impostorScale;
}
yield return ShipStatus.Instance.CosmeticsCache.PopulateFromPlayers();
@@ -306,45 +321,43 @@ public class HideAndSeekMode : AbstractGameMode
playerSlot.gameObject.SetActive(false);
}
__instance.HideAndSeekPanels.SetActive(false);
__instance.CrewmateRules.SetActive(false);
__instance.ImpostorRules.SetActive(false);
introCutscene.HideAndSeekPanels.SetActive(false);
introCutscene.CrewmateRules.SetActive(false);
introCutscene.ImpostorRules.SetActive(false);
HnsMusicHandler.Instance.StartMusicWithIntro();
var hideTimer = 10f;
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor)
{
__instance.HideAndSeekTimerText.gameObject.SetActive(true);
introCutscene.HideAndSeekTimerText.gameObject.SetActive(true);
PoolablePlayer poolablePlayer;
AnimationClip anim;
if (AprilFoolsMode.ShouldHorseAround())
{
poolablePlayer = __instance.HorseWrangleVisualSuit;
poolablePlayer = introCutscene.HorseWrangleVisualSuit;
poolablePlayer.gameObject.SetActive(true);
poolablePlayer.SetBodyType(PlayerBodyTypes.Seeker);
anim = __instance.HnSSeekerSpawnHorseAnim;
__instance.HorseWrangleVisualPlayer.SetBodyType(PlayerBodyTypes.Normal);
__instance.HorseWrangleVisualPlayer.UpdateFromPlayerData(
anim = introCutscene.HnSSeekerSpawnHorseAnim;
introCutscene.HorseWrangleVisualPlayer.SetBodyType(PlayerBodyTypes.Normal);
introCutscene.HorseWrangleVisualPlayer.UpdateFromPlayerData(
PlayerControl.LocalPlayer.Data,
PlayerControl.LocalPlayer.CurrentOutfitType,
PlayerMaterial.MaskType.None,
false,
null,
false);
}
else if (AprilFoolsMode.ShouldLongAround())
{
poolablePlayer = __instance.HideAndSeekPlayerVisual;
poolablePlayer = introCutscene.HideAndSeekPlayerVisual;
poolablePlayer.gameObject.SetActive(true);
poolablePlayer.SetBodyType(PlayerBodyTypes.LongSeeker);
anim = __instance.HnSSeekerSpawnLongAnim;
anim = introCutscene.HnSSeekerSpawnLongAnim;
}
else
{
poolablePlayer = __instance.HideAndSeekPlayerVisual;
poolablePlayer = introCutscene.HideAndSeekPlayerVisual;
poolablePlayer.gameObject.SetActive(true);
poolablePlayer.SetBodyType(PlayerBodyTypes.Seeker);
anim = __instance.HnSSeekerSpawnAnim;
anim = introCutscene.HnSSeekerSpawnAnim;
}
poolablePlayer.SetBodyCosmeticsVisible(false);
@@ -352,16 +365,14 @@ public class HideAndSeekMode : AbstractGameMode
PlayerControl.LocalPlayer.Data,
PlayerControl.LocalPlayer.CurrentOutfitType,
PlayerMaterial.MaskType.None,
false,
null,
false);
SpriteAnim component = poolablePlayer.GetComponent<SpriteAnim>();
var component = poolablePlayer.GetComponent<SpriteAnim>();
poolablePlayer.gameObject.SetActive(true);
poolablePlayer.ToggleName(false);
component.Play(anim, 1f);
component.Play(anim);
while (hideTimer > 0f)
{
__instance.HideAndSeekTimerText.text = Mathf.RoundToInt(hideTimer).ToString();
introCutscene.HideAndSeekTimerText.text = Mathf.RoundToInt(hideTimer).ToString(CultureInfo.InvariantCulture);
hideTimer -= Time.deltaTime;
yield return null;
}
@@ -373,42 +384,45 @@ public class HideAndSeekMode : AbstractGameMode
{
if (impostor != null)
{
impostor.AnimateCustom(__instance.HnSSeekerSpawnHorseInGameAnim);
impostor.AnimateCustom(introCutscene.HnSSeekerSpawnHorseInGameAnim);
}
}
else if (AprilFoolsMode.ShouldLongAround())
{
if (impostor != null)
{
impostor.AnimateCustom(__instance.HnSSeekerSpawnLongInGameAnim);
impostor.AnimateCustom(introCutscene.HnSSeekerSpawnLongInGameAnim);
}
}
else if (impostor != null)
{
impostor.AnimateCustom(__instance.HnSSeekerSpawnAnim);
impostor.AnimateCustom(introCutscene.HnSSeekerSpawnAnim);
impostor.cosmetics.SetBodyCosmeticsVisible(false);
}
}
ShipStatus.Instance.StartSFX();
HnsMusicHandler.Instance.OnGameStart();
HnsDangerMeter.Instance.OnGameStart();
UnityEngine.Object.Destroy(__instance.gameObject);
UnityEngine.Object.Destroy(introCutscene.gameObject);
}
/// <inheritdoc/>
public override void Initialize()
{
deadPlayerCount = 0;
ShipStatus.Instance.BreakEmergencyButton();
PlayerControl.LocalPlayer.SetKillTimer(0.01f);
if (HudManager.InstanceExists)
{
HudManager.Instance.gameObject.AddComponent<HnsMusicHandler>();
HudManager.Instance.gameObject.AddComponent<HideAndSeekHudHelper>();
HudManager.Instance.gameObject.AddComponent<HnsDangerMeter>();
}
if (!HudManager.InstanceExists) return;
HudManager.Instance.gameObject.AddComponent<HnsMusicHandler>();
HudManager.Instance.gameObject.AddComponent<HideAndSeekHudHelper>();
HudManager.Instance.gameObject.AddComponent<HnsDangerMeter>();
}
/// <inheritdoc/>
public override MapOptions GetMapOptions()
{
MapOptions mapOptions = new MapOptions
MapOptions mapOptions = new()
{
Mode = MapOptions.Modes.Normal,
};
@@ -419,27 +433,34 @@ public class HideAndSeekMode : AbstractGameMode
mapOptions.IncludeDeadBodies = false;
mapOptions.ShowLivePlayerPosition = false;
}
return mapOptions;
}
/// <inheritdoc/>
public override void CheckGameEnd(out bool runOriginal, LogicGameFlowNormal instance)
{
runOriginal = false;
if (Helpers.GetAlivePlayers().Count(x => x.Data.Role.IsImpostor) == 0)
var players = Helpers.GetAlivePlayers();
if (!players.Any(x => x.Data.Role.IsImpostor))
{
instance.Manager.RpcEndGame(GameOverReason.ImpostorDisconnect, !DataManager.Player.Ads.HasPurchasedAdRemoval);
}
if (Helpers.GetAlivePlayers().Count(x => !x.Data.Role.IsImpostor) != 0)
if (players.Any(x => !x.Data.Role.IsImpostor))
{
if (HideAndSeekHudHelper.Instance.AllTimersExpired())
{
instance.Manager.RpcEndGame(GameOverReason.HideAndSeek_CrewmatesByTimer, !DataManager.Player.Ads.HasPurchasedAdRemoval);
}
return;
}
instance.Manager.RpcEndGame(GameOverReason.HideAndSeek_ImpostorsByKills, !DataManager.Player.Ads.HasPurchasedAdRemoval);
}
/// <inheritdoc/>
public override PlayerBodyTypes GetBodyType(PlayerControl player)
{
if (player == null || player.Data == null || player.Data.Role == null)
@@ -448,47 +469,39 @@ public class HideAndSeekMode : AbstractGameMode
{
return PlayerBodyTypes.Horse;
}
if (AprilFoolsMode.ShouldLongAround())
{
return PlayerBodyTypes.Long;
}
return PlayerBodyTypes.Normal;
}
else if (AprilFoolsMode.ShouldHorseAround())
if (AprilFoolsMode.ShouldHorseAround())
{
if (player.Data.Role.IsImpostor)
{
return PlayerBodyTypes.Normal;
}
return PlayerBodyTypes.Horse;
return player.Data.Role.IsImpostor ? PlayerBodyTypes.Normal : PlayerBodyTypes.Horse;
}
else if (AprilFoolsMode.ShouldLongAround())
if (AprilFoolsMode.ShouldLongAround())
{
if (player.Data.Role.IsImpostor)
{
return PlayerBodyTypes.LongSeeker;
}
return PlayerBodyTypes.Long;
}
else
{
if (player.Data.Role.IsImpostor)
{
return PlayerBodyTypes.Seeker;
}
return PlayerBodyTypes.Normal;
return player.Data.Role.IsImpostor ? PlayerBodyTypes.LongSeeker : PlayerBodyTypes.Long;
}
return player.Data.Role.IsImpostor ? PlayerBodyTypes.Seeker : PlayerBodyTypes.Normal;
}
/// <inheritdoc/>
public override void UpdateTaskPanel(TaskPanelBehaviour instance)
{
instance.background.transform.localScale = (instance.taskText.textBounds.size.x > 0f)
? new Vector3(instance.taskText.textBounds.size.x + 0.2f, instance.taskText.textBounds.size.y + 0.2f, 1f)
: Vector3.zero;
Vector3 vector = instance.background.sprite.bounds.extents;
var vector = instance.background.sprite.bounds.extents;
vector.y = -vector.y;
vector = vector.Mul(instance.background.transform.localScale);
instance.background.transform.localPosition = vector;
Vector3 vector2 = instance.tab.sprite.bounds.extents;
var vector2 = instance.tab.sprite.bounds.extents;
vector2 = vector2.Mul(instance.tab.transform.localScale);
vector2.y = -vector2.y;
vector2.x += vector.x * 2f;
@@ -502,14 +515,9 @@ public class HideAndSeekMode : AbstractGameMode
var xPos = -instance.background.sprite.bounds.size.x * instance.background.transform.localScale.x;
instance.closedPosition = new Vector3(xPos, yPos, instance.closedPosition.z);
instance.openPosition = new Vector3(instance.openPosition.x, yPos, instance.openPosition.z);
if (instance.open)
{
instance.timer = Mathf.Min(1f, instance.timer + Time.deltaTime / instance.animationTimeSeconds);
}
else
{
instance.timer = Mathf.Max(0f, instance.timer - Time.deltaTime / instance.animationTimeSeconds);
}
instance.timer = instance.open
? Mathf.Min(1f, instance.timer + (Time.deltaTime / instance.animationTimeSeconds))
: Mathf.Max(0f, instance.timer - (Time.deltaTime / instance.animationTimeSeconds));
Vector3 relativePos = new(
Mathf.SmoothStep(instance.closedPosition.x, instance.openPosition.x, instance.timer),
@@ -518,7 +526,10 @@ public class HideAndSeekMode : AbstractGameMode
instance.transform.localPosition =
AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos);
}
private int deadPlayerCount;
/// <inheritdoc/>
public override void OnPlayerDeath(PlayerControl player, bool assignGhostRole)
{
base.OnPlayerDeath(player, assignGhostRole);
+61 -15
View File
@@ -1,9 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.GameOptions;
@@ -25,6 +29,12 @@ public abstract class AbstractOptionGroup
/// </summary>
public abstract string GroupName { get; }
/// <summary>
/// Gets the sprite of the group that is displayed in the match info guide.
/// </summary>
[HideFromIl2Cpp]
public virtual LoadableAsset<Sprite> Sprite => MiraAssets.IconSettings;
/// <summary>
/// Gets the Optionable type of the group.
/// </summary>
@@ -33,10 +43,9 @@ public abstract class AbstractOptionGroup
/// <summary>
/// Gets a value indicating whether the group should be shown in the modifiers menu.
/// </summary>
// TODO: make this not a boolean
#pragma warning disable S1133
[Obsolete("Use ParentMenu instead.")]
#pragma warning restore S1133
// Completed: make this not a boolean
[Obsolete("Use ParentMenu instead.", true)]
[SuppressMessage("Info Code Smell", "S1133:Deprecated code should be removed", Justification = "Retained.")]
public virtual bool ShowInModifiersMenu => false;
/// <summary>
@@ -47,7 +56,43 @@ public abstract class AbstractOptionGroup
/// <summary>
/// Gets the function that determines whether the group should be visible or not.
/// </summary>
public virtual Func<bool> GroupVisible => () => true;
public virtual Func<bool> GroupVisible => DelegateUtilities.AlwaysTrue;
/// <summary>
/// Gets a value indicating whether this group's options are shown in the Match Info Guide.
/// </summary>
public virtual bool ShowInMatchInfoGuide => ParentMenu is MenuCategory.Game;
/// <summary>
/// Gets the name title of the group when it is in the match info guide.
/// </summary>
public virtual string RoleGuideTitle => GroupName;
/// <summary>
/// Gets the key used to combine this group with other option groups on one Match Info Guide page.
/// Groups are combined only with groups from the same plugin that use the same non-empty key.
/// </summary>
public virtual string? MatchInfoGuidePageKey => null;
/// <summary>
/// Gets the title of the Match Info Guide page. For groups sharing a page key, the first
/// group in menu order supplies the page title.
/// </summary>
public virtual string MatchInfoGuidePageTitle => RoleGuideTitle;
/// <summary>
/// Gets the ordering priority of this Match Info Guide page. Lower values appear first
/// among pages with the same menu category and group priority. Groups sharing a page key
/// use the lowest page-order value among those groups.
/// </summary>
public virtual int MatchInfoGuidePageOrder => int.MaxValue;
/// <summary>
/// Gets the number of columns used to lay out options on this Match Info Guide page.
/// For a combined page, groups are placed into these columns. Values are limited to 1-6;
/// for groups sharing a page key, the first group in menu order supplies this value.
/// </summary>
public virtual int MatchInfoGuidePageColumns => 2;
/// <summary>
/// Gets the group <see cref="Color"/>. This is used to color the group in the options menu.
@@ -76,7 +121,8 @@ public abstract class AbstractOptionGroup
/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu.
/// </summary>
/// <typeparam name="T">The type of the optionable that this group contains.</typeparam>
public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOptionable
public abstract class AbstractOptionGroup<T> : AbstractOptionGroup
where T : IOptionable
{
/// <inheritdoc />
public override Type OptionableType => typeof(T);
@@ -87,14 +133,15 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
get
{
var type = typeof(T);
if (type == typeof(BaseModifier))
if (typeof(BaseModifier).IsAssignableFrom(type))
{
return MenuCategory.Modifiers;
}
else if (type == typeof(RoleBehaviour))
else if (typeof(RoleBehaviour).IsAssignableFrom(type))
{
return MenuCategory.Roles;
}
return MenuCategory.Game;
}
}
@@ -104,7 +151,8 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu.
/// </summary>
/// <typeparam name="T">The custom role that the group is for.</typeparam>
public abstract class AbstractRoleOptionGroup<T>() : AbstractOptionGroup<T> where T : ICustomRole
public abstract class AbstractRoleOptionGroup<T> : AbstractOptionGroup<T>
where T : ICustomRole
{
/// <inheritdoc />
public override Type OptionableType => typeof(T);
@@ -118,11 +166,9 @@ public abstract class AbstractRoleOptionGroup<T>() : AbstractOptionGroup<T> wher
get
{
var role = CustomRoleManager.CustomMiraRoles.FirstOrDefault(x => x.GetType() == OptionableType);
if (role == null)
{
return new(new Color(0.7333f, 0.7333f, 0.7333f, 1));
}
return new(role.RoleColor, role.Configuration.IconTmp);
return role == null
? new(new Color(0.7333f, 0.7333f, 0.7333f, 1))
: new(role.RoleColor, role.Configuration.IconTmp);
}
}
}
@@ -133,7 +179,7 @@ public abstract class AbstractRoleOptionGroup<T>() : AbstractOptionGroup<T> wher
public enum MenuCategory
{
/// <summary>
/// Placeholder for indexing purposes. Options don't exist in the presset tab.
/// Placeholder for indexing purposes. Options don't exist in the preset tab.
/// </summary>
Preset,
@@ -0,0 +1,50 @@
using System;
using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute to set the <see cref="IModdedOption.ChangedEvent"/> delegate in a <see cref="ModdedOptionAttribute"/> or <see cref="ModdedOptionListAttribute"/>.
/// </summary>
/// <param name="methodName">The method to get the changed event from.</param>
/// <param name="holderType">The type the method is in.</param>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ModdedOptionChangedAttribute(string? methodName = null, Type? holderType = null)
: ModdedOptionMemberAttribute(methodName, holderType, "Changed")
{
internal Delegate? GetChanged(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
{
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
{
if (notFound)
Error($"Method {MemberName} does not exist in {type.FullName}");
return null;
}
var valueType = option.GetValueType();
if (!ValidateSignature(method, typeof(void), valueType))
return null;
var delegateType = typeof(Action<>).MakeGenericType(valueType);
return CompileDynamicDelegate(type, method, instanceGetter, typeof(void), [valueType], delegateType, "Changed");
}
internal Func<int, Delegate?>? GetListChanged(AbstractOptionGroup group, PropertyInfo property, Type valueType)
{
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
{
if (notFound)
Error($"Method {MemberName} does not exist in {type.FullName}");
return null;
}
if (!ValidateListSignature(method, typeof(void), out var hasIndex, valueType))
return null;
var delegateType = typeof(Action<>).MakeGenericType(valueType);
return CompileDynamicListDelegate(type, method, instanceGetter, typeof(void), [valueType], delegateType, "ListChanged", hasIndex);
}
}
@@ -0,0 +1,50 @@
using System;
using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute to set the <see cref="IModdedOption.Getter"/> delegate in a <see cref="ModdedOptionAttribute"/> or <see cref="ModdedOptionListAttribute"/>.
/// </summary>
/// <param name="methodName">The method to get the getter from.</param>
/// <param name="holderType">The type the method is in.</param>
[AttributeUsage(AttributeTargets.Property)]
public sealed class ModdedOptionGetterAttribute(string? methodName = null, Type? holderType = null)
: ModdedOptionMemberAttribute(methodName, holderType, "Getter")
{
internal Delegate? GetGetter(AbstractOptionGroup group, PropertyInfo property, IModdedOption option)
{
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
{
if (notFound)
Error($"Method {MemberName} does not exist in {type.FullName}");
return null;
}
var valueType = option.GetValueType();
if (!ValidateSignature(method, valueType, valueType))
return null;
var delegateType = typeof(Func<,>).MakeGenericType(valueType, valueType);
return CompileDynamicDelegate(type, method, instanceGetter, valueType, [valueType], delegateType, "Getter");
}
internal Func<int, Delegate?>? GetListGetter(AbstractOptionGroup group, PropertyInfo property, Type valueType)
{
if (ResolveMethod(group, property, out var type, out var instanceGetter, out var notFound) is not { } method)
{
if (notFound)
Error($"Method {MemberName} does not exist in {type.FullName}");
return null;
}
if (!ValidateListSignature(method, valueType, out var hasIndex, valueType))
return null;
var delegateType = typeof(Func<,>).MakeGenericType(valueType, valueType);
return CompileDynamicListDelegate(type, method, instanceGetter, valueType, [valueType], delegateType, "ListGetter", hasIndex);
}
}
@@ -0,0 +1,428 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using MiraAPI.Utilities;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Base attribute for resolving and compiling option delegate members (<see cref="IModdedOption.Visible"/>, <see cref="IModdedOption.Getter"/>, <see cref="IModdedOption.Setter"/>, and <see cref="IModdedOption.ChangedEvent"/>).
/// </summary>
/// <param name="memberName">The name of the target member, or <see langword="null"/> to use the property name with <paramref name="defaultSuffix"/>.</param>
/// <param name="holderType">The type containing the target member, or <see langword="null"/> to use the declaring <see cref="AbstractOptionGroup"/> type.</param>
/// <param name="defaultSuffix">The default suffix appended to the property name when <paramref name="memberName"/> is not specified.</param>
[AttributeUsage(AttributeTargets.Property)]
public abstract class ModdedOptionMemberAttribute(string? memberName, Type? holderType, string defaultSuffix) : Attribute
{
private static readonly Dictionary<MethodInfo, Delegate> DelegateCache = [];
/// <summary>
/// A helper class to bind a specific list index to a dynamically compiled delegate.
/// </summary>
/// <param name="index">The index to bind.</param>
public sealed class DelegateIndexClosure(int index)
{
/// <summary>
/// The bound index.
/// </summary>
[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "No need to be private.")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")]
public readonly int Index = index;
}
/// <summary>
/// Exposes the index field so child attributes can emit instructions to read it.
/// </summary>
protected static readonly FieldInfo IndexClosureField = typeof(DelegateIndexClosure).GetField(nameof(DelegateIndexClosure.Index))!;
/// <summary>
/// Gets the resolved name of the target member.
/// </summary>
protected string? MemberName { get; private set; } = memberName;
/// <summary>
/// Validates that a method matches the expected return type and parameter types.
/// </summary>
/// <param name="method">The method to validate.</param>
/// <param name="expectedReturnType">The expected return type of the method.</param>
/// <param name="expectedParamTypes">The expected types of the parameters of the method.</param>
/// <returns><c>true</c> if the method is valid; <c>false</c> otherwise.</returns>
protected bool ValidateSignature(MethodInfo method, Type expectedReturnType, params Type[] expectedParamTypes)
{
if (method.ReturnType != expectedReturnType)
{
Error($"{MemberName}'s return type must be {expectedReturnType.Name}.");
return false;
}
var parameters = method.GetParameters();
if (parameters.Length != expectedParamTypes.Length)
{
Error($"{MemberName} must have exactly {expectedParamTypes.Length} parameter(s).");
return false;
}
for (var i = 0; i < expectedParamTypes.Length; i++)
{
if (parameters[i].ParameterType == expectedParamTypes[i])
continue;
Error($"{MemberName}'s parameter at index {i} must be {expectedParamTypes[i].Name}.");
return false;
}
return true;
}
/// <summary>
/// Validates that a list method matches the expected return type, base parameter types, and an optional integer index.
/// </summary>
/// <param name="method">The method to validate.</param>
/// <param name="expectedReturnType">The expected return type of the method.</param>
/// <param name="hasIndex">When the method returns, a flag that indicates if the method has an index parameter.</param>
/// <param name="expectedParamTypes">The expected types of the parameters of the method.</param>
/// <returns><c>true</c> if the method is valid; <c>false</c> otherwise.</returns>
protected bool ValidateListSignature(MethodInfo method, Type expectedReturnType, out bool hasIndex, params Type[] expectedParamTypes)
{
hasIndex = false;
if (method.ReturnType != expectedReturnType)
{
Error($"{MemberName}'s return type must be {expectedReturnType.Name}.");
return false;
}
var parameters = method.GetParameters();
if (parameters.Length < expectedParamTypes.Length || parameters.Length > expectedParamTypes.Length + 1)
{
Error($"{MemberName} must have {expectedParamTypes.Length} parameter(s), optionally with an extra trailing int parameter.");
return false;
}
for (var i = 0; i < expectedParamTypes.Length; i++)
{
if (parameters[i].ParameterType == expectedParamTypes[i])
continue;
Error($"{MemberName}'s parameter at index {i} must be {expectedParamTypes[i].Name}.");
return false;
}
if (parameters.Length == expectedParamTypes.Length + 1)
{
if (parameters[^1].ParameterType != typeof(int))
{
Error($"{MemberName}'s final parameter must be an int (the list index).");
return false;
}
hasIndex = true;
}
return true;
}
/// <summary>
/// Resolves the target <see cref="MemberInfo"/> (property, field or method) and its associated singleton instance getter, if applicable.
/// </summary>
/// <param name="group">The option group instance the property belongs to.</param>
/// <param name="property">The option property decorated with this attribute.</param>
/// <param name="type">When this method returns, contains the resolved <see cref="Type"/> expected to hold the target member.</param>
/// <param name="instanceGetter">
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
/// if the resolved member is an instance member on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
/// </param>
/// <param name="notFound">When this method returns, contains a flag that indicates if the member was not found, as null can be returned from other circumstances too.</param>
/// <returns>The resolved <see cref="MemberInfo"/> if found; otherwise, <see langword="null"/>.</returns>
[SuppressMessage("Major Code Smell", "S3011:Reflection should not be used to increase accessibility of classes, methods, or fields", Justification = "It is safe.")]
protected MemberInfo? ResolveMember(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter, out bool notFound)
{
var groupType = group.GetType();
var flags = BindingFlags.Static | BindingFlags.Public;
MemberName ??= property.Name + defaultSuffix;
type = holderType ?? groupType;
instanceGetter = null;
if (type.IsAssignableTo(typeof(AbstractOptionGroup)))
{
flags |= BindingFlags.Instance;
if (type == groupType)
flags |= BindingFlags.NonPublic;
instanceGetter = typeof(OptionGroupSingleton<>)
.MakeGenericType(type)
.GetProperty(nameof(OptionGroupSingleton<>.Instance))!
.GetGetMethod();
}
else if (!type.IsStatic())
{
flags |= BindingFlags.Instance;
var canSearchPrivates = type.GetCustomAttribute<SearchPrivateAttribute>() != null;
if (canSearchPrivates)
flags |= BindingFlags.NonPublic;
var getterFlags = BindingFlags.Static | BindingFlags.Public | (canSearchPrivates ? BindingFlags.NonPublic : BindingFlags.Default);
foreach (var method in type.GetMethods(getterFlags))
{
if (method.GetParameters().Length > 0 || (method.GetCustomAttribute<InstanceGetterAttribute>() == null && method.Name != "GetInstance"))
continue;
instanceGetter = method;
break;
}
if (instanceGetter == null)
{
foreach (var prop in type.GetProperties(getterFlags))
{
if (prop.GetCustomAttribute<InstanceGetterAttribute>() == null && prop.Name != "Instance")
continue;
var getMethod = prop.GetGetMethod(canSearchPrivates);
if (getMethod == null)
continue;
instanceGetter = getMethod;
break;
}
}
if (instanceGetter == null)
{
Error($"No valid static instance getter (method GetInstance or property Instance) found in instanced type {type.Name}. Try renaming the method/property, or decorate it with InstanceGetterAttribute.");
notFound = false;
return null;
}
if (!instanceGetter.ReturnType.IsAssignableTo(type))
{
Error($"Found instance getter {instanceGetter.Name}, but the return type did not match what was expected ({type.Name}).");
notFound = false;
return null;
}
}
else if (type.GetCustomAttribute<SearchPrivateAttribute>() != null)
{
flags |= BindingFlags.NonPublic;
}
var member = type.GetMember(MemberName, flags).FirstOrDefault();
notFound = true;
if (member == null)
return null;
notFound = false;
if (instanceGetter == null)
return member;
bool isStatic;
if (member is FieldInfo field)
{
isStatic = field.IsStatic;
}
else
{
var method = (member as PropertyInfo)?.GetGetMethod(true) ?? member as MethodInfo;
isStatic = method is { IsStatic: true };
}
if (isStatic)
instanceGetter = null;
return member;
}
/// <summary>
/// Resolves the target <see cref="MethodInfo"/> and its associated singleton instance getter, if applicable.
/// </summary>
/// <param name="group">The option group instance the property belongs to.</param>
/// <param name="property">The option property decorated with this attribute.</param>
/// <param name="type">When this method returns, contains the resolved <see cref="Type"/> expected to hold the target method.</param>
/// <param name="instanceGetter">
/// When this method returns, contains the <see cref="OptionGroupSingleton{T}.Instance"/> getter <see cref="MethodInfo"/>
/// if the resolved method is an instance method on an <see cref="AbstractOptionGroup"/>; otherwise, <see langword="null"/>.
/// </param>
/// <param name="notFound">When this method returns, contains a flag that indicates if the member was not found, as null can be returned from other circumstances too.</param>
/// <returns>The resolved <see cref="MethodInfo"/> if found; otherwise, <see langword="null"/>.</returns>
protected MethodInfo? ResolveMethod(AbstractOptionGroup group, PropertyInfo property, out Type type, out MethodInfo? instanceGetter, out bool notFound)
{
return ResolveMember(group, property, out type, out instanceGetter, out notFound) as MethodInfo;
}
/// <summary>
/// Compiles a <see cref="DynamicMethod"/> using IL generation to invoke the resolved static or instance <paramref name="method"/>.
/// </summary>
/// <param name="ownerType">The <see cref="Type"/> that owns the generated <see cref="DynamicMethod"/> and holds the target method.</param>
/// <param name="method">The target <see cref="MethodInfo"/> to invoke.</param>
/// <param name="instanceGetter">
/// The static getter <see cref="MethodInfo"/> used to push the target instance onto the evaluation stack for instance methods,
/// or <see langword="null"/> if <paramref name="method"/> is static.
/// </param>
/// <param name="returnType">The return <see cref="Type"/> of the generated delegate (or <see cref="void"/> for actions).</param>
/// <param name="paramTypes">The parameter types accepted by the generated delegate.</param>
/// <param name="delegateType">The concrete delegate <see cref="Type"/> to create (e.g., <see cref="Func{T, TResult}"/> or <see cref="Action{T}"/>).</param>
/// <param name="suffix">A descriptive suffix appended to the generated <see cref="DynamicMethod"/> name for debugging and stack traces.</param>
/// <param name="forwardParameters">Whether to push the delegate parameters onto the stack before invoking <paramref name="method"/>.</param>
/// <param name="postProcessCall">An optional callback to emit additional IL instructions before returning.</param>
/// <returns>A compiled <see cref="Delegate"/> of type <paramref name="delegateType"/> bound to the generated IL method.</returns>
protected static Delegate CompileDynamicDelegate(
Type ownerType,
MethodInfo method,
MethodInfo? instanceGetter,
Type returnType,
Type[] paramTypes,
Type delegateType,
string suffix,
bool forwardParameters = true,
Action<ILGenerator>? postProcessCall = null)
{
if (postProcessCall == null && DelegateCache.TryGetValue(method, out var cachedDelegate))
return cachedDelegate;
var dm = new DynamicMethod($"{ownerType.Name}_{method.Name}_{suffix}", returnType, paramTypes, ownerType, true);
var il = dm.GetILGenerator();
if (!method.IsStatic && instanceGetter != null)
il.Emit(OpCodes.Call, instanceGetter);
if (forwardParameters)
{
for (byte i = 0; i < paramTypes.Length; i++)
{
var loadOp = i switch
{
0 => OpCodes.Ldarg_0,
1 => OpCodes.Ldarg_1,
2 => OpCodes.Ldarg_2,
3 => OpCodes.Ldarg_3,
_ => OpCodes.Ldarg_S,
};
if (loadOp == OpCodes.Ldarg_S)
il.Emit(loadOp, i);
else
il.Emit(loadOp);
}
}
var callOp = method.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
il.Emit(callOp, method);
postProcessCall?.Invoke(il);
il.Emit(OpCodes.Ret);
var compiledDelegate = dm.CreateDelegate(delegateType);
if (postProcessCall == null)
DelegateCache[method] = compiledDelegate;
return compiledDelegate;
}
/// <summary>
/// Compiles a factory function that provides a <see cref="Delegate"/> for each index in an option list.
/// If <paramref name="hasIndexParameter"/> is <see langword="false"/> and <paramref name="postProcessListCall"/> is <see langword="null"/>,
/// a single <see cref="Delegate"/> is compiled once and reused for all indices.
/// </summary>
/// <param name="ownerType">The <see cref="Type"/> that owns the generated <see cref="DynamicMethod"/> and holds the target method.</param>
/// <param name="method">The target <see cref="MethodInfo"/> to invoke.</param>
/// <param name="instanceGetter">
/// The static getter <see cref="MethodInfo"/> used to push the target instance onto the evaluation stack for instance methods,
/// or <see langword="null"/> if <paramref name="method"/> is static.
/// </param>
/// <param name="returnType">The return <see cref="Type"/> of the generated delegate (or <see cref="void"/> for actions).</param>
/// <param name="baseParamTypes">The parameter types accepted by the generated delegate.</param>
/// <param name="delegateType">The concrete delegate <see cref="Type"/> to create.</param>
/// <param name="suffix">A descriptive suffix appended to the generated <see cref="DynamicMethod"/> name for debugging and stack traces.</param>
/// <param name="hasIndexParameter">Whether the target <paramref name="method"/> takes the list <see langword="int"/> index as its final parameter.</param>
/// <param name="postProcessCall">An optional index-independent callback to emit additional IL instructions after <paramref name="method"/> is called.</param>
/// <param name="postProcessListCall">An optional index-dependent callback to emit additional IL instructions after <paramref name="method"/> is called.</param>
/// <returns>A factory function that returns a compiled <see cref="Delegate"/> of type <paramref name="delegateType"/> for a given list index.</returns>
protected static Func<int, Delegate> CompileDynamicListDelegate(
Type ownerType,
MethodInfo method,
MethodInfo? instanceGetter,
Type returnType,
Type[] baseParamTypes,
Type delegateType,
string suffix,
bool hasIndexParameter,
Action<ILGenerator>? postProcessCall = null,
Action<ILGenerator>? postProcessListCall = null)
{
if (!hasIndexParameter && postProcessListCall is null)
{
var sharedDelegate = CompileDynamicDelegate(
ownerType,
method,
instanceGetter,
returnType,
baseParamTypes,
delegateType,
suffix,
baseParamTypes.Length > 0,
postProcessCall);
return _ => sharedDelegate;
}
var fullParamTypes = new Type[baseParamTypes.Length + 1];
fullParamTypes[0] = typeof(DelegateIndexClosure);
Array.Copy(baseParamTypes, 0, fullParamTypes, 1, baseParamTypes.Length);
var dm = new DynamicMethod($"{ownerType.Name}_{method.Name}_{suffix}_List", returnType, fullParamTypes, ownerType, true);
var il = dm.GetILGenerator();
if (!method.IsStatic && instanceGetter != null)
il.Emit(OpCodes.Call, instanceGetter);
for (var i = 0; i < baseParamTypes.Length; i++)
{
var argPos = (byte)(i + 1);
var loadOp = argPos switch
{
1 => OpCodes.Ldarg_1,
2 => OpCodes.Ldarg_2,
3 => OpCodes.Ldarg_3,
_ => OpCodes.Ldarg_S,
};
if (loadOp == OpCodes.Ldarg_S)
il.Emit(loadOp, argPos);
else
il.Emit(loadOp);
}
if (hasIndexParameter)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, IndexClosureField);
}
var callOp = method.IsStatic ? OpCodes.Call : OpCodes.Callvirt;
il.Emit(callOp, method);
postProcessCall?.Invoke(il);
postProcessListCall?.Invoke(il);
il.Emit(OpCodes.Ret);
return index => dm.CreateDelegate(delegateType, new DelegateIndexClosure(index));
}
}
Loaded 100 of 394 files, more files were not shown because too many files have changed in this diff. Show more