├── .gitattributes
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── copy-metadata.py
├── cspell.json
├── docs
├── api.md
└── quick-start.md
├── main.lua
├── metadata.xml
├── release.py
├── release.sh
├── resources
├── gfx
│ └── ui
│ │ └── modconfig
│ │ ├── arrows.png
│ │ ├── backselect.png
│ │ ├── black.png
│ │ ├── menu.anm2
│ │ ├── menu.png
│ │ ├── menu_overlay.png
│ │ ├── offset.png
│ │ ├── popup_thin_large.png
│ │ ├── popup_thin_medium.png
│ │ ├── popup_thin_small.png
│ │ ├── popup_wide_large.png
│ │ ├── popup_wide_medium.png
│ │ ├── popup_wide_small.png
│ │ ├── slider.png
│ │ └── strike.png
└── previewfile_1603631350.jpg
├── scripts
├── inputhelper.lua
├── modconfig.lua
└── screenhelper.lua
└── steam
├── screenshots
├── 1603631350_preview_250900_20210110125025_1.png
├── 1603631350_preview_250900_20210113160746_1.png
├── 1603631350_preview_250900_20210113160751_1.png
├── 1603631350_preview_250900_20210113160753_1.png
├── 1603631350_preview_250900_20210113160757_1.png
├── 1603631350_preview_250900_20210113160811_1.png
└── 1603631350_preview_250900_20210113160813_1.png
└── thumbnail.jpg
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Prevent Windows systems from cloning this repository with "\r\n" line endings
2 | core.autocrlf=false
3 |
4 | # Convert all files to use "\n" line endings
5 | * text=auto eol=lf
6 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build_and_lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout the repository
10 | uses: actions/checkout@v3
11 |
12 | # https://github.com/IsaacScript/isaac-steam-workshop-upload
13 | - name: Upload the mod to Steam Workshop (if this is a release commit)
14 | uses: IsaacScript/isaac-steam-workshop-upload@v2
15 | if: "contains(github.event.head_commit.message, 'chore: release') && github.event_name != 'pull_request'"
16 | with:
17 | mod_path: .
18 | ignore_files: cspell.json,release.py,release.sh,steam
19 | change_note: "Version: {VERSION}\n\nChanges for this mod are [url=https://github.com/Zamiell/isaac-mod-config-menu]tracked on GitHub[/url]."
20 | env:
21 | CONFIG_VDF_CONTENTS: ${{ secrets.CONFIG_VDF_CONTENTS }}
22 |
23 | discord:
24 | name: Discord Failure Notification
25 | needs: [build_and_lint]
26 | if: failure()
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: sarisia/actions-status-discord@v1
30 | with:
31 | webhook: ${{ secrets.DISCORD_WEBHOOK }}
32 | status: failure
33 | title: ""
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS-specific artifacts
2 | .DS_Store
3 | thumbs.db
4 |
5 | # Isaac-related files
6 | disable.it
7 | *.dat
8 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | // These are Visual Studio Code extensions that are intended to be used with this particular
2 | // repository
3 | // https://go.microsoft.com/fwlink/?LinkId=827846
4 | {
5 | "recommendations": [
6 | "sumneko.lua", // Lua language server
7 | "filloax.isaac-lua-api-vscode", // Isaac API definitions
8 | "streetsidesoftware.code-spell-checker", // A spell-checker extension based on CSpell
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // These are Visual Studio Code settings that should apply to this particular repository
2 | {
3 | // ----------------
4 | // Vanilla settings
5 | // ----------------
6 |
7 | "editor.rulers": [100],
8 | "editor.tabSize": 2,
9 |
10 | "files.associations": {
11 | "*.anm2": "xml"
12 | },
13 |
14 | // Linux line endings are used in this project
15 | "files.eol": "\n",
16 |
17 | // Automatically removing all trailing whitespace when saving a file
18 | "files.trimTrailingWhitespace": true,
19 |
20 | // -----------------
21 | // Language settings
22 | // -----------------
23 |
24 | // Automatically run the formatter when certain files are saved.
25 | "[lua]": {
26 | "editor.defaultFormatter": "sumneko.lua",
27 | "editor.formatOnSave": true,
28 | "editor.tabSize": 2,
29 | },
30 |
31 | // Isaac globals, auto-defined from Filloax's extension.
32 | "Lua.diagnostics.globals": [
33 | "ActionTriggers",
34 | "ActiveSlot",
35 | "BabySubType",
36 | "BackdropType",
37 | "BatterySubType",
38 | "BedSubType",
39 | "BitSet128",
40 | "BombSubType",
41 | "BombVariant",
42 | "ButtonAction",
43 | "CacheFlag",
44 | "Card",
45 | "Challenge",
46 | "ChampionColor",
47 | "ChestSubType",
48 | "CoinSubType",
49 | "CollectibleType",
50 | "Color",
51 | "CppContainer",
52 | "DamageFlag",
53 | "Difficulty",
54 | "Direction",
55 | "DoorSlot",
56 | "DoorState",
57 | "DoorVariant",
58 | "EffectVariant",
59 | "Entity",
60 | "EntityBomb",
61 | "EntityCollisionClass",
62 | "EntityEffect",
63 | "EntityFamiliar",
64 | "EntityFlag",
65 | "EntityGridCollisionClass",
66 | "EntityKnife",
67 | "EntityLaser",
68 | "EntityNPC",
69 | "EntityPartition",
70 | "EntityPickup",
71 | "EntityPlayer",
72 | "EntityProjectile",
73 | "EntityPtr",
74 | "EntityRef",
75 | "EntityTear",
76 | "EntityType",
77 | "FamiliarVariant",
78 | "Font",
79 | "Game",
80 | "GameStateFlag",
81 | "GetPtrHash",
82 | "GridCollisionClass",
83 | "GridEntity",
84 | "GridEntityDesc",
85 | "GridEntityDoor",
86 | "GridEntityPit",
87 | "GridEntityPoop",
88 | "GridEntityPressurePlate",
89 | "GridEntityRock",
90 | "GridEntitySpikes",
91 | "GridEntityTNT",
92 | "GridEntityType",
93 | "GridRooms",
94 | "HUD",
95 | "HeartSubType",
96 | "Input",
97 | "InputHook",
98 | "Isaac",
99 | "ItemConfig",
100 | "ItemPool",
101 | "ItemPoolType",
102 | "ItemType",
103 | "KColor",
104 | "KeySubType",
105 | "Keyboard",
106 | "LaserOffset",
107 | "LaserSubType",
108 | "Level",
109 | "LevelCurse",
110 | "LevelStage",
111 | "LevelStateFlag",
112 | "LocustSubtypes",
113 | "ModCallbacks",
114 | "Mouse",
115 | "Music",
116 | "MusicManager",
117 | "NpcState",
118 | "NullItemID",
119 | "Options",
120 | "PathFinder",
121 | "PickupPrice",
122 | "PickupVariant",
123 | "PillColor",
124 | "PillEffect",
125 | "PlayerForm",
126 | "PlayerSpriteLayer",
127 | "PlayerType",
128 | "PlayerTypes",
129 | "PoopPickupSubType",
130 | "PoopSpellType",
131 | "ProjectileFlags",
132 | "ProjectileParams",
133 | "ProjectileVariant",
134 | "QueueItemData",
135 | "REPENTANCE",
136 | "RNG",
137 | "Random",
138 | "RandomVector",
139 | "RegisterMod",
140 | "RenderMode",
141 | "Room",
142 | "RoomConfig",
143 | "RoomDescriptor",
144 | "RoomShape",
145 | "RoomTransitionAnim",
146 | "RoomType",
147 | "SFXManager",
148 | "SackSubType",
149 | "SeedEffect",
150 | "Seeds",
151 | "ShockwaveParams",
152 | "SkinColor",
153 | "SortingLayer",
154 | "SoundEffect",
155 | "Sprite",
156 | "StageType",
157 | "StartDebug",
158 | "TearFlags",
159 | "TearParams",
160 | "TearVariant",
161 | "TemporaryEffect",
162 | "TemporaryEffects",
163 | "TrinketType",
164 | "UseFlag",
165 | "Vector",
166 | "WeaponType",
167 | "EID",
168 | "CustomGameOver",
169 | "Encyclopedia",
170 | "DetailedRespawn",
171 | "AwaitingTextInput",
172 | "DeadSeaScrollsMenu"
173 | ],
174 | }
175 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 piber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mod Config Menu Pure
2 |
3 |
4 |
5 |
6 | ## Introduction
7 |
8 | Mod Config Menu Pure is a library for [The Binding of Isaac: Repentance](https://store.steampowered.com/app/1426300/The_Binding_of_Isaac_Repentance/) that allows other mods to have a settings menu.
9 |
10 | You can find this library [on the Steam Workshop](https://steamcommunity.com/sharedfiles/filedetails/?id=2681875787).
11 |
12 | Credit goes to piber20 for originally creating this library and Chifilly for updating it for Repentance.
13 |
14 | This is a forked version of Mod Config Menu by Zamiel that removes all of the hacks that override internal Lua functionality, which causes problems with other things in the Isaac ecosystem. For this reason, it is called the "Pure" version.
15 |
16 | As of [vanilla patch v1.7.9b](https://bindingofisaacrebirth.fandom.com/wiki/V1.7.9b), other version of Mod Config Menu will not work anymore, like [Mod Config Menu Continued](https://steamcommunity.com/workshop/filedetails/?id=2487535818), so it is recommended to use this version instead.
17 |
18 |
19 |
20 | ## Using Mod Config Menu for Players
21 |
22 | If you are a player of Isaac mods, then using Mod Config Menu should be straightforward. The controls are as follows:
23 |
24 | ### Keyboard
25 |
26 | - By default, you can open the menu by pressing L. (This keyboard binding is customizable from the "Mod Config Menu" sub-menu.) F10 will also always open the menu, which cannot be changed.
27 | - Use the arrow keys or WASD keys to move around.
28 | - E, space, or enter can be used to select an item.
29 | - Esc, backspace, or Q can be used to go back.
30 |
31 | ### Controller
32 |
33 | - By default, you can open the menu by pressing down the right control stick (i.e. R3). (This controller binding is customizable from the "Mod Config Menu" sub-menu.)
34 | - Both control sticks can be used to move around.
35 | - The "a" button can be used to select an item.
36 | - The "b" button can be used to go back.
37 |
38 | By default, there will be two sub-menus installed: "General" and "Mod Config Menu". If you have other mods installed, they may add additional menus.
39 |
40 |
41 |
42 | ## Using Mod Config Menu as a Mod Developer
43 |
44 | In order to use Mod Config Menu Pure inside of your mod, do not use the `require` or `dofile` or `pcall` or `loadfile` functions. Rather, simply check to see if the global variable of `ModConfigMenu` exists, with something along the lines of:
45 |
46 | ```lua
47 | local MOD_NAME = "My Mod"
48 | local VERSION = "1.0.0"
49 |
50 | local function setupMyModConfigMenuSettings()
51 | if ModConfigMenu == nil then
52 | return
53 | end
54 |
55 | ModConfigMenu.AddSpace(MOD_NAME, "Info")
56 | ModConfigMenu.AddText(MOD_NAME, "Info", function() return MOD_NAME end)
57 | ModConfigMenu.AddSpace(MOD_NAME, "Info")
58 | ModConfigMenu.AddText(MOD_NAME, "Info", function() return "Version " .. VERSION end)
59 | end
60 | ```
61 |
62 | For more information:
63 |
64 | - See [the quick start guide](docs/quick-start.md).
65 | - Also see [the API documentation](docs/api.md).
66 |
67 |
68 |
69 | ## Troubleshooting
70 |
71 | Note that the "Pure" version of Mod Config Menu will not work properly if:
72 |
73 | - You have subscribed to the "Pure" version and you subscribed to a different version at the same time, which will cause a conflict.
74 | - You are subscribed to a mod that uses a standalone version of Mod Config Menu, which will cause a conflict.
75 | - You are subscribed to a mod uses the `require` or `dofile` or `pcall` or `loadfile` functions to initialize or invoke Mod Config Menu.
76 |
77 |
78 |
79 | ## FAQ
80 |
81 | ### Does it work with Repentance?
82 |
83 | Yes.
84 |
85 | ### Does it work with the latest version of Repentance?
86 |
87 | Yes. In [version 1.7.9b](https://bindingofisaacrebirth.fandom.com/wiki/V1.7.9b), the `loadfile` function was removed from the game. But unlike other versions of Mod Config Menu, Mod Config Menu Pure does not use `loadfile` (or any other hacks), so this version continues to work as it did before.
88 |
89 | ### Does it work with Afterbirth+?
90 |
91 | No, because it uses the Repentance-only API for getting the HUD offset.
92 |
93 | ### What do I do if Mod Config Menu Pure causes errors or otherwise does not seem to get loaded properly by a particular mod?
94 |
95 | This is probably because the mod is using the `require` or `dofile` or `pcall` or `loadfile` functions to initialize or invoke Mod Config Menu. Contact the individual mod author to fix this.
96 |
97 | ### What do I do if Mod Config Menu Pure works properly to configure settings, but does not save the settings for future runs?
98 |
99 | Mod Config Menu Pure is not responsible for saving the configuration data of other mods. Doing that is up to the other mods. Thus, if another mod's settings are not being properly saved, then you need to take that issue to the specific mod's developer.
100 |
101 | ### What is Mod Config Menu Continued?
102 |
103 | The original version of Mod Config Menu was made by piber20. [Mod Config Menu Continued](https://steamcommunity.com/sharedfiles/filedetails/?id=2487535818) is an updated version made by Chifilly with the goal of making it work with the Repentance DLC and fixing some bugs. Mod Config Menu Pure is an updated version of Mod Config Menu Continued with the goal of fixing yet more bugs. Thus, Mod Config Menu Continued is not the same thing as Mod Config Menu Pure.
104 |
105 | As of December 8, 2022, Mod Config Menu Continued no longer works with the latest version of the game, so you should use Mod Config Menu Pure instead.
106 |
107 | ### Should I subscribe to multiple versions of Mod Config Menu at the same time?
108 |
109 | No. You should only subscribe to one specific version of Mod Config Menu at a time.
110 |
111 | ### How do I open Mod Config Menu?
112 |
113 | See [the "Using Mod Config Menu for Players" section](#using-mod-config-menu-for-players) above.
114 |
115 | If the default keyboard/controller bindings do not work, then it is possible you have previously remapped them to something else. In this case, you can use the F10 button on the keyboard, which will always open the menu. Then, you can configure the keyboard/controller bindings to the exact thing that you want.
116 |
117 | ### What do I do if saving settings for a mod does not work between game launches?
118 |
119 | Mod Config Menu is not in charge of saving any data besides the ones in the "General" and "Mod Config Menu" pages. If an individual mod does not properly save its data, then you should contact the author of that mod.
120 |
121 | ### Does this have the same functionality (i.e. API) as the other versions of Mod Config Menu?
122 |
123 | Yes. However, it might not work as a drop-in replacement for mods that use the `require` or `dofile` or `pcall` or `loadfile` functions to initialize or invoke Mod Config Menu. Another common issue is using deprecated properties like `ModConfigMenuOptionType.BOOLEAN` instead of `ModConfigMenu.OptionType.BOOLEAN`. If you are a mod author and you want to switch to the pure version, you should test everything thoroughly.
124 |
125 | ### What does it mean to "remove API overrides"?
126 |
127 | The original version overwrote some of the Lua and Isaac API functions, such as `pcall` and `RequireMod`. This version does not overwrite any API functions.
128 |
129 | ### How do I tell what version of Mod Config Menu Pure I have?
130 |
131 | There are 3 ways:
132 |
133 | - You can see the version in the console.
134 | - You can see the version in the game's "log.txt" file. (See the next section.)
135 | - You can see the version at the top of the mod's Lua file. (See the next section.)
136 |
137 | ### Where is the game's "log.txt" file located?
138 |
139 | By default, it is located at the following path:
140 |
141 | ```text
142 | C:\Users\[username]\Documents\My Games\Binding of Isaac Repentance\log.txt
143 | ```
144 |
145 | ### Where is the Lua code for the mod located?
146 |
147 | By default, it is located at the following path:
148 |
149 | ```text
150 | C:\Program Files (x86)\Steam\steamapps\common\The Binding of Isaac Rebirth\mods\!!mod config menu_2681875787\scripts\modconfig.lua
151 | ```
152 |
153 | ### Where is the save data for the mod located?
154 |
155 | By default, it is located at the following path:
156 |
157 | ```text
158 | C:\Program Files (x86)\Steam\steamapps\common\The Binding of Isaac Rebirth\data\!!mod config menu\save#.dat
159 | ```
160 |
161 | The `#` corresponds to the number of the save slot that you are playing on.
162 |
163 | ### How do I reset my Mod Config Menu settings?
164 |
165 | You need to delete the save data file that corresponds to the Isaac save slot that you are on. See the previous section.
166 |
167 | ### Why doesn't Mod Config Menu work for me?
168 |
169 | It works for everyone else, so it has to be something wrong with you. Start by uninstalling the game, completely removing all leftover game files, reinstalling the game, and then only subscribing to this mod on the workshop and nothing else. At this point, you will probably have no errors, so you can then start to introduce other things piece by piece until you find the thing that is causing the problem. For more information on where the various game files are located, see [my directories and save files explanation](https://github.com/Zamiell/isaac-faq/blob/main/directories-and-save-files.md).
170 |
171 | ### What was changed in the last update?
172 |
173 | Look at the [commit history](https://github.com/Zamiell/isaac-mod-config-menu/commits/main).
174 |
175 | ### Why doesn't Mod Config Menu pause the gane when it is open? Why does Mod Config Menu not stop the in-game timer? Why can't I open Mod Config Menu in a room with enemies?
176 |
177 | The Isaac modding API does not allow mods to pause the game or control the in-game timer. Thus, for this reason, mod config menu is only allowed to be open when there are no enemies in the room. Subsequently, you should be mindful that the in-game timer will continue to increase when Mod Config Menu is open, so you might e.g. miss the Boss Rush.
178 |
--------------------------------------------------------------------------------
/copy-metadata.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 |
4 | SCRIPT_PATH = os.path.realpath(__file__)
5 | SOURCE_MOD_DIRECTORY = os.path.dirname(SCRIPT_PATH)
6 | TARGET_MOD_DIRECTORY = "C:\\Program Files (x86)\\Steam\\steamapps\\common\\The Binding of Isaac Rebirth\\mods\\!!mod config menu"
7 |
8 | METADATA_XML = "metadata.xml"
9 | SOURCE_METADATA_XML = os.path.join(SOURCE_MOD_DIRECTORY, METADATA_XML)
10 | TARGET_METADATA_XML = os.path.join(TARGET_MOD_DIRECTORY, METADATA_XML)
11 |
12 | # Copy back the "metadata.xml" file, since it will have an incremented version number.
13 | shutil.copyfile(TARGET_METADATA_XML, SOURCE_METADATA_XML)
14 |
15 | # Clean up the target mod directory.
16 | shutil.rmtree(TARGET_MOD_DIRECTORY)
17 |
--------------------------------------------------------------------------------
/cspell.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
3 | "version": "0.2",
4 | "files": [
5 | "**"
6 | ],
7 | "ignorePaths": [
8 | "*.anm2",
9 | "*.pyc",
10 | "*.wav",
11 | ".git/**"
12 | ],
13 | "words": [
14 | "Bigbooks",
15 | "BOMBDROP",
16 | "Chifilly",
17 | "dofile",
18 | "DPAD",
19 | "ERRORBUZZ",
20 | "FULLSCREEN",
21 | "Gamepad",
22 | "inputhelper",
23 | "ipairs",
24 | "keybind",
25 | "loadfile",
26 | "MCM",
27 | "MENUBACK",
28 | "MENUCONFIRM",
29 | "MENUDOWN",
30 | "MENULEFT",
31 | "MENURIGHT",
32 | "MENUUP",
33 | "Metatables",
34 | "modconfig",
35 | "modconfigmenu",
36 | "pcall",
37 | "pftempestasevencondensed",
38 | "piber",
39 | "PILLCARD",
40 | "Popen",
41 | "Rebindable",
42 | "screenhelper",
43 | "SHOOTDOWN",
44 | "SHOOTLEFT",
45 | "SHOOTRIGHT",
46 | "SHOOTUP",
47 | "Spritesheet",
48 | "steamapps",
49 | "teammeatfont",
50 | "Uncategorized",
51 | "userdata",
52 | "WASD",
53 | "Wofsauge",
54 | "Zamiel",
55 | "Zamiell"
56 | ]
57 | }
58 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 |
4 |
5 | When Mod Config Menu is installed, a global `ModConfigMenu` is available which contains all the following functions.
6 |
7 | The settings added will show up in the order the functions are called.
8 |
9 | ___Disclaimer__: Some of the following information may be incorrect. I quickly threw this together in a couple of hours, and only did some quick parsing of the code to try and figure out how it works and the flow. Anyone is free to create an issue or pull request with corrections._
10 |
11 | ### Functions
12 |
13 | #### Category Functions
14 |
15 | ##### GetCategoryIDByName([categoryName](#categoryname): string): number
16 |
17 | Returns the category ID based off of the `categoryName` provided.
18 | Returns `nil` if not a valid category.
19 | Returns what was provided if `categoryName` is not a `string`.
20 |
21 | ##### UpdateCategory([categoryName](#categoryname): string, [categoryData](#categorydata): [categoryData](#categorydata))
22 |
23 | Updates a category with the supplied data.
24 |
25 | ##### SetCategoryInfo([categoryName](#categoryname): string, info: string)
26 |
27 | Changes category info.
28 |
29 | ##### RemoveCategory([categoryName](#categoryname): string)
30 |
31 | Removes a category entirely.
32 |
33 | #### Subcategory Functions
34 |
35 | ##### GetSubcategoryIDByName(category: string|number, [subcategoryName](#subcategoryname): string): number
36 |
37 | Returns the subcategory ID based off of the `category` (which is either the category name or category ID) and `subcategoryName` provided.
38 | Returns `nil` if not a valid category or subcategory.
39 | Returns what was provided if `category` is not a `string` or `number` or `subcategoryName` is not a `string`.
40 |
41 | ##### UpdateSubcategory([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, [subcategoryData](#subcategorydata): [subcategoryData](#subcategorydata))
42 |
43 | Updates a subcategory with the supplied data.
44 |
45 | ##### RemoveSubcategory([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string)
46 |
47 | Removes a subcategory entirely.
48 |
49 | #### Setting Functions
50 |
51 | ##### AddSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, [settingTable](#settingtable): [settingTable](#settingtable))
52 |
53 | Add a new setting to the supplied category and subcategory with the provided data.
54 |
55 | ##### RemoveSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, settingAttribute: string)
56 |
57 | Remove the setting at the provided category, subcategory and attribute
58 |
59 | ##### AddText([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, text: string, color: RGBArray)
60 |
61 | Add text into the mod config menu under the provided category and subcategory.
62 |
63 | ##### AddTitle([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, text: string, color: RGBArray)
64 |
65 | Add a title to the mod config menu under the provided category and subcategory.
66 |
67 | ##### AddSpace([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string)
68 |
69 | Add a space to the mod config menu under the provided category and subcategory.
70 |
71 | ##### SimpleAddSetting(settingType: [OptionType](#optiontype), [categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, minValue: number, maxValue: number, modifyBy: number, defaultValue: any, displayText: string, [displayValueProxies](#displayvalueproxies), [displayDevice](#displaydevice): boolean, info: string, color: RGBArray, functionName: string)
72 |
73 | Create a setting without using a table.
74 |
75 | `functionName` = The name of the function it was called from (only used in error messages, and _really_ only used internally).
76 |
77 | Any `Add` functions that take `categoryName` and `configTableAttribute` will store its data in `ModConfigMenu.Config[categoryName][configTableAttribute]`. Other versions of Mod Config Menu may auto-save this data to file. However, this version of Mod Config Menu does not. Make sure you save and load your data as appropriate.
78 |
79 | _All of the individual `Add*` functions below can be achieved with just `AddSetting` and providing the `Type` parameter in [`settingTable`](#settingtable) to be a [`ModConfigMenu.OptionType`](#optiontype). That is also the way I recommend, because I haven't been able to fully understand the code yet, so I don't know what some of the parameters are for, and how the "overrides" are set up._
80 | _Any help figuring out what all the parameters and "overrides" are so I can make this readme more accurate would be appreciated._
81 |
82 | ##### AddBooleanSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, defaultValue: boolean, displayText: string, [displayValueProxies](#displayvalueproxies): table, info: string, color: RGBArray)
83 |
84 | Add a boolean setting under the provided category and subcategory.
85 |
86 | ##### AddNumberSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, minValue: number, maxValue: number, modifyBy: number, defaultValue: number, displayText: string, [displayValueProxies](#displayvalueproxies), info: string, color: RGBArray)
87 |
88 | Add a number value setting under the provided category and subcategory.
89 |
90 | ##### AddScrollSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, defaultValue: number, displayText: string, info: string, color: RGBArray)
91 |
92 | Add a slider setting under the provided category and subcategory.
93 |
94 | ##### AddKeyboardSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, defaultValue: number, displayText: string, [displayDevice](#displaydevice): boolean, info: string, color: RGBArray)
95 |
96 | Add a keyboard keybinding setting.
97 |
98 | ##### AddControllerSetting([categoryName](#categoryname): string, [subcategoryName](#subcategoryname): string, configTableAttribute: ?, defaultValue: number, displayText: string, [displayDevice](#displaydevice): boolean, info: string, color: RGBArray)
99 |
100 | Add a controller keybinding setting.
101 |
102 | ### Variables and Parameters
103 |
104 | #### Common Parameters
105 |
106 | ##### categoryName
107 |
108 | What needs to be chosen on the left to go to your settings menu.
109 |
110 | ##### subcategoryName
111 |
112 | The secondary section within your tab the setting is in. This is a tab list at the top of your menu.
113 |
114 | ##### categoryData
115 |
116 | A table of data for the category.
117 |
118 | ```lua
119 | {
120 | Name = string -- the name of the category
121 | Info = string -- the description of the category
122 | IsOld = boolean -- not sure of the purpose, only seems to turn the text red
123 | }
124 | ```
125 |
126 | ##### subcategoryData
127 |
128 | A table of data for the subcategory.
129 |
130 | ```lua
131 | Name = string -- the name of the category
132 | Info = string -- the description of the category
133 | ```
134 |
135 | ##### settingTable
136 |
137 | A table of data for the setting.
138 |
139 | ```lua
140 | {
141 | -- the type of the setting, see OptionType for more information
142 | Type = ModConfigMenu.OptionType,
143 |
144 | -- the identifier for the setting
145 | Attribute = string,
146 |
147 | -- the default value for the setting
148 | Default = any,
149 |
150 | -- a function that returns the current value of the setting
151 | CurrentSetting = function(),
152 |
153 | -- the minimum value of numeric settings
154 | Minimum = number,
155 |
156 | -- the maximum value of numeric settings
157 | Maximum = number,
158 |
159 | -- a function that returns a string of how the setting will display in the settings menu
160 | Display = function(),
161 |
162 | -- a function that is called whenever the setting is changed (can be used to save your settings for example)
163 | OnChange = function(),
164 |
165 | -- a table of strings that's used as the information for the setting
166 | Info = { string },
167 |
168 | -- the color of the setting (values are floats between 0 and 1)
169 | Color = { r, g, b },
170 | }
171 | ```
172 |
173 | ##### displayValueProxies
174 |
175 | A table that denotes what text will be displayed based on the setting value as the index.
176 |
177 | ```lua
178 | -- this will make "true" show as "On" and "false" show as "Off"
179 | {
180 | [true] = "On",
181 | [false] = "Off"
182 | }
183 |
184 | -- or
185 |
186 | -- this will make 0 show "Sometimes", 1 show "Never" and 2 show "Always"
187 | {
188 | [0] = "Sometimes",
189 | [1] = "Never",
190 | [2] = "Always"
191 | },
192 | ```
193 |
194 | ##### displayDevice
195 |
196 | Whether the display text should be suffixed with the control device (`(keyboard)` or `(controller)`).
197 |
198 | #### Enums
199 |
200 | ##### OptionType
201 |
202 | All these option types are in the `ModConfigMenu.OptionType` enum.
203 |
204 | `TEXT` = Plain text.
205 | `SPACE` = A paragraph-type gap rendered in the menu.
206 | `SCROLL` = A slider-bar for numeric values.
207 | `BOOLEAN` = A boolean (true or false).
208 | `NUMBER` = A numeric value.
209 | `KEYBIND_KEYBOARD` = A keybind for keyboards.
210 | `KEYBIND_CONTROLLER` = A keybind for controllers.
211 | `TITLE` = Heading-style text.
212 |
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Mod Config Menu
2 |
3 |
4 |
5 | If you want to add config options to your Binding of Isaac mod, then instead of programming your own menu system from scratch, you can register your mod's configuration options with [Mod Config Menu](https://github.com/Zamiell/isaac-mod-config-menu), or MCM for short. MCM is a mod that provides an extendable in-game menu.
6 |
7 |
8 |
9 | ## The `ModConfigMenu` Global Variable
10 |
11 | The first thing you want to do is check for the existence of the `ModConfigMenu` global variable. If it exists then you can proceed.
12 |
13 | ```lua
14 | local function modConfigMenuInit()
15 | if ModConfigMenu == nil then
16 | return
17 | end
18 |
19 | -- Insert code here to add the menu options for your mod
20 | end
21 | ```
22 |
23 |
24 |
25 | ## Common Scenarios
26 |
27 | In most cases, you will be providing the player with multiple choice options. This can take the form of "on/off" or "A/B/C".
28 |
29 | ### On/Off
30 |
31 | On/off looks like this:
32 |
33 | ```lua
34 | -- Specify a table of the MCM settings for this mod, along with their default values
35 | local settings = {
36 | myBoolean = false,
37 | }
38 |
39 | ModConfigMenu.AddSetting(
40 | "My Settings Page", -- This should be unique for your mod
41 | "Tab 1", -- If you don't want multiple tabs, then set this to nil
42 | {
43 | Type = ModConfigMenu.OptionType.BOOLEAN,
44 | CurrentSetting = function()
45 | return settings.myBoolean
46 | end,
47 | Display = function()
48 | return "My Boolean: " .. (settings.myBoolean and "on" or "off")
49 | end,
50 | OnChange = function(b)
51 | settings.myBoolean = b
52 | end,
53 | Info = { -- This can also be a function instead of a table
54 | "Info on 1st line",
55 | "More info on 2nd line",
56 | }
57 | }
58 | )
59 | ```
60 |
61 | ### A/B/C (multiple choice)
62 |
63 | If you need 3 or more options then that looks like this:
64 |
65 | ```lua
66 | local choices = {
67 | "A",
68 | "B",
69 | "C",
70 | }
71 |
72 | -- Specify a table of the MCM settings for this mod, along with their default values
73 | local settings = {
74 | myMultipleChoice = choices[1] -- The first choice by default
75 | }
76 |
77 | local function getTableIndex(tbl, val)
78 | for i, v in ipairs(tbl) do
79 | if v == val then
80 | return i
81 | end
82 | end
83 |
84 | return 0
85 | end
86 |
87 | ModConfigMenu.AddSetting(
88 | "My Settings Page",
89 | "Tab 1",
90 | {
91 | Type = ModConfigMenu.OptionType.NUMBER,
92 | CurrentSetting = function()
93 | return getTableIndex(choices, settings.myMultipleChoice)
94 | end,
95 | Minimum = 1,
96 | Maximum = #choices,
97 | Display = function()
98 | return "My Multiple Choice: " .. settings.myMultipleChoice
99 | end,
100 | OnChange = function(n)
101 | settings.myMultipleChoice = choices[n]
102 | end,
103 | -- Text in the "Info" section will automatically word-wrap, unlike in the main section above
104 | Info = { "Info on 1st line" }
105 | }
106 | )
107 | ```
108 |
109 | ### Scroll Bar
110 |
111 | A scroll bar is a multiple choice option that renders as a scroll bar with 10 bars. The player can choose between 0 and 10 out of 10 for a total of 11 options.
112 |
113 | This is commonly used to get a decimal number between 0 and 1 (by i.e. dividing by 10).
114 |
115 | ```lua
116 | local settings = {}
117 | settings.myScrollBar = 0
118 |
119 | ModConfigMenu.AddSetting(
120 | "My Settings Page",
121 | "Tab 1",
122 | {
123 | Type = ModConfigMenu.OptionType.SCROLL,
124 | CurrentSetting = function()
125 | return settings.myScrollBar
126 | end,
127 | Display = function()
128 | return "My Scroll Bar: $scroll" .. settings.myScrollBar
129 | end,
130 | OnChange = function(n)
131 | settings.myScrollBar = n
132 | end,
133 | Info = { "Info on 1st line" }
134 | }
135 | )
136 | ```
137 |
138 |
139 |
140 | ## Layout
141 |
142 | You can add a title, text, or vertical spacer like this:
143 |
144 | ```lua
145 | ModConfigMenu.AddTitle("My Settings Page", "Tab 1", "My Title")
146 | ModConfigMenu.AddText("My Settings Page", "Tab 1", "My Text")
147 | ModConfigMenu.AddSpace("My Settings Page", "Tab 1")
148 | ```
149 |
150 |
151 |
152 | ## Saving and Loading
153 |
154 | ### Saving
155 |
156 | You are responsible for saving your settings, which can be as simple as:
157 |
158 | ```lua
159 | local json = require("json")
160 |
161 | local mod = RegisterMod("MyMod", 1)
162 |
163 | -- If your mod does not store any other save data, then you can simply run the "save" function in
164 | -- every MCM "OnChange" function. Otherwise, you need to run it on both the "OnChange" functions and
165 | -- MC_PRE_GAME_EXIT.
166 | local function save()
167 | -- TODO: use pcall to gracefully handle failure
168 | local jsonString = json.encode(settings)
169 | mod:SaveData(jsonString)
170 | end
171 | ```
172 |
173 | ### Loading
174 |
175 | You are also responsible for loading your saved settings, which can be as simple as:
176 |
177 | ```lua
178 | -- If your mod does not store any other save data, then you can simply run the "load" function
179 | -- during mod initialization. Otherwise, you need to run it on both initialization and at the
180 | -- beginning of a run.
181 | local function load()
182 | if not mod:HasData() then
183 | return
184 | end
185 |
186 | -- TODO: use pcall to gracefully handle failure
187 | -- TODO: add checks for validate the data
188 | local jsonString = mod:LoadData()
189 | settings = json.decode(jsonString)
190 | end
191 | ```
192 |
193 | ### IsaacScript
194 |
195 | If you are coding your mod using TypeScript, then the [IsaacScript](https://isaacscript.github.io/) standard library has a save data manager that you should use instead of handling saving and loading yourself manually.
196 |
197 | ```ts
198 | // Config.ts
199 | export class Config {
200 | feature1 = true;
201 | feature2 = 1;
202 | // and so on
203 | }
204 |
205 | // modConfigMenu.ts
206 | const v = {
207 | persistent: {
208 | config: new Config(),
209 | }
210 | }
211 |
212 | function modConfigMenuInit() {
213 | saveDataManager("modConfigMenu", v);
214 | }
215 | ```
216 |
217 |
218 |
219 | ## Further Reading
220 |
221 | - See the [Mod Reference](https://wofsauge.github.io/IsaacDocs/rep/ModReference.html) documentation for related saving and loading information.
222 | - See the [API documentation](api.md).
223 |
--------------------------------------------------------------------------------
/main.lua:
--------------------------------------------------------------------------------
1 | -- This script handles saving for the standalone version of Mod Config Menu.
2 |
3 | local mod = RegisterMod("Mod Config Menu Standalone", 1)
4 |
5 | ModConfigMenu = {}
6 | ModConfigMenu.StandaloneMod = mod
7 |
8 | require("scripts.modconfig")
9 |
--------------------------------------------------------------------------------
/metadata.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | !! Mod Config Menu Pure
4 | !!mod config menu
5 | 2681875787
6 | [h1][b]Mod Config Menu Pure[/b][/h1]
7 |
8 | [h2][b]Introduction[/b][/h2]
9 |
10 | Mod Config Menu is a library to that allows other mods to have a settings menu. You should only subscribe to this mod if you have other mods that use it.
11 |
12 | [h2][b]Installing[/b][/h2]
13 |
14 | Click on the green "Subscribe" button above, which will cause Steam to automatically download the mod. Then, the next time you open the game, the mod will appear in the "Mods" submenu.
15 |
16 | [h2][b]More Info[/b][/h2]
17 |
18 | For more information, read [url=https://github.com/Zamiell/isaac-mod-config-menu/blob/main/README.md]the README file for this mod on GitHub[/url].
19 |
20 | [h1][b]Before posting a comment below, read the entire README on GitHub. Comments asking questions that have already been covered in the README will be deleted.[/b][/h1]
21 |
22 | 112
23 | Public
24 |
25 |
--------------------------------------------------------------------------------
/release.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import shutil
4 | import subprocess
5 | import sys
6 |
7 | SCRIPT_PATH = os.path.realpath(__file__)
8 | SOURCE_MOD_DIRECTORY = os.path.dirname(SCRIPT_PATH)
9 | LUA_FILE_PATH = os.path.join(SOURCE_MOD_DIRECTORY, "scripts", "modconfig.lua")
10 | METADATA_XML_PATH = os.path.join(SOURCE_MOD_DIRECTORY, "metadata.xml")
11 |
12 |
13 | def main():
14 | if not is_git_clean():
15 | printf(
16 | "Error: The current working directory must be clean before releasing a new version. Please push your changes to Git."
17 | )
18 | sys.exit(1)
19 |
20 | # Validate that we can push and pull to the repository and that all commits are remotely synced.
21 | subprocess.run(["git", "branch", "--set-upstream-to=origin/main", "main"])
22 | subprocess.run(["git", "pull", "--rebase"])
23 | subprocess.run(["git", "push", "--set-upstream", "origin", "main"])
24 |
25 | new_version = increment_lua_version()
26 | set_metadata_xml_version(new_version)
27 |
28 | subprocess.run(["git", "add", "--all"])
29 | subprocess.run(["git", "commit", "--message", f"chore: release {new_version}"])
30 | subprocess.run(["git", "push", "--set-upstream", "origin", "main"])
31 |
32 | printf(f"Released version: {new_version}")
33 |
34 |
35 | def is_git_clean():
36 | stdout_bytes = subprocess.check_output(["git", "status", "--short"])
37 | stdout = stdout_bytes.decode("utf-8")
38 | trimmed_stdout = stdout.strip()
39 | return len(trimmed_stdout) == 0
40 |
41 |
42 | def increment_lua_version():
43 | if not os.path.exists(LUA_FILE_PATH):
44 | error(f"Failed to find the Lua file at: {LUA_FILE_PATH}")
45 |
46 | with open(LUA_FILE_PATH) as f:
47 | text = f.read()
48 |
49 | match = re.search(r"local VERSION = (\d+)", text)
50 | if not match:
51 | error(f"Failed to find the version in the Lua file at: {LUA_FILE_PATH}")
52 |
53 | version_string = match.group(1)
54 | version_number = int(version_string)
55 | new_version = version_number + 1
56 |
57 | text = re.sub(
58 | f"local VERSION = {version_string}",
59 | f"local VERSION = {new_version}",
60 | text,
61 | )
62 | text = re.sub("local IS_DEV = true", "local IS_DEV = false", text)
63 |
64 | with open(LUA_FILE_PATH, "w", newline="\n") as f:
65 | f.write(text)
66 |
67 | return new_version
68 |
69 |
70 | def set_metadata_xml_version(new_version: str):
71 | if not os.path.exists(METADATA_XML_PATH):
72 | error(f'Failed to find the "metadata.xml" file at: {METADATA_XML_PATH}')
73 |
74 | with open(METADATA_XML_PATH) as f:
75 | text = f.read()
76 |
77 | text = re.sub(
78 | r"(.+?)",
79 | f"{new_version}",
80 | text,
81 | )
82 |
83 | with open(METADATA_XML_PATH, "w", newline="\n") as f:
84 | f.write(text)
85 |
86 |
87 | def error(msg: str):
88 | printf(f"Error: {msg}")
89 | sys.exit(1)
90 |
91 |
92 | def printf(*args):
93 | print(*args, flush=True)
94 |
95 |
96 | if __name__ == "__main__":
97 | main()
98 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e # Exit on any errors
4 |
5 | # Get the directory of this script:
6 | # https://stackoverflow.com/questions/59895/getting-the-source-directory-of-a-bash-script-from-within
7 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
8 |
9 | cd "$DIR"
10 |
11 | python "$DIR/release.py"
12 |
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/arrows.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/backselect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/backselect.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/black.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/menu.anm2:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/menu.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/menu_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/menu_overlay.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/offset.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_thin_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_thin_large.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_thin_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_thin_medium.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_thin_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_thin_small.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_wide_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_wide_large.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_wide_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_wide_medium.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_wide_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/popup_wide_small.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/slider.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/strike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/gfx/ui/modconfig/strike.png
--------------------------------------------------------------------------------
/resources/previewfile_1603631350.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/resources/previewfile_1603631350.jpg
--------------------------------------------------------------------------------
/scripts/inputhelper.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- IMPORTANT: DO NOT EDIT THIS FILE!!! --
3 | ------------------------------------------------------------------------------
4 | -- This file relies on other versions of itself being the same. --
5 | -- If you need something in this file changed, please let the creator know! --
6 | ------------------------------------------------------------------------------
7 |
8 | -- CODE STARTS BELOW --
9 |
10 | -------------
11 | -- version --
12 | -------------
13 |
14 | local fileVersion = 1
15 |
16 | --prevent older/same version versions of this script from loading
17 | if InputHelper and InputHelper.Version >= fileVersion then
18 | return InputHelper
19 | end
20 |
21 | if not InputHelper then
22 | InputHelper = {}
23 | InputHelper.Version = fileVersion
24 | elseif InputHelper.Version < fileVersion then
25 | -- handle old versions
26 | if InputHelper.HandleForceActionPressed then
27 | InputHelper.Mod:RemoveCallback(ModCallbacks.MC_INPUT_ACTION, InputHelper.HandleForceActionPressed)
28 | end
29 |
30 | InputHelper.Version = fileVersion
31 | end
32 |
33 | -----------
34 | -- setup --
35 | -----------
36 |
37 | InputHelper.Mod = InputHelper.Mod or RegisterMod("Input Helper", 1)
38 |
39 | ---------
40 | --enums--
41 | ---------
42 |
43 | Controller = Controller or {}
44 | Controller.DPAD_LEFT = 0
45 | Controller.DPAD_RIGHT = 1
46 | Controller.DPAD_UP = 2
47 | Controller.DPAD_DOWN = 3
48 | Controller.BUTTON_A = 4
49 | Controller.BUTTON_B = 5
50 | Controller.BUTTON_X = 6
51 | Controller.BUTTON_Y = 7
52 | Controller.BUMPER_LEFT = 8
53 | Controller.TRIGGER_LEFT = 9
54 | Controller.STICK_LEFT = 10
55 | Controller.BUMPER_RIGHT = 11
56 | Controller.TRIGGER_RIGHT = 12
57 | Controller.STICK_RIGHT = 13
58 | Controller.BUTTON_BACK = 14
59 | Controller.BUTTON_START = 15
60 |
61 | ----------------
62 | --id to string--
63 | ----------------
64 |
65 | InputHelper.KeyboardToString = InputHelper.KeyboardToString or {}
66 |
67 | for key, num in pairs(Keyboard) do
68 | local keyString = key
69 |
70 | local keyStart, keyEnd = string.find(keyString, "KEY_")
71 | keyString = string.sub(keyString, keyEnd + 1, string.len(keyString))
72 |
73 | keyString = string.gsub(keyString, "_", " ")
74 |
75 | InputHelper.KeyboardToString[num] = keyString
76 | end
77 |
78 | InputHelper.ControllerToString = InputHelper.ControllerToString or {}
79 |
80 | for button, num in pairs(Controller) do
81 | local buttonString = button
82 |
83 | if string.match(buttonString, "BUTTON_") then
84 | local buttonStart, buttonEnd = string.find(buttonString, "BUTTON_")
85 | buttonString = string.sub(buttonString, buttonEnd + 1, string.len(buttonString))
86 | end
87 |
88 | if string.match(buttonString, "BUMPER_") then
89 | local bumperStart, bumperEnd = string.find(buttonString, "BUMPER_")
90 | buttonString = string.sub(buttonString, bumperEnd + 1, string.len(buttonString)) .. "_BUMPER"
91 | end
92 |
93 | if string.match(buttonString, "TRIGGER_") then
94 | local triggerStart, triggerEnd = string.find(buttonString, "TRIGGER_")
95 | buttonString = string.sub(buttonString, triggerEnd + 1, string.len(buttonString)) .. "_TRIGGER"
96 | end
97 |
98 | if string.match(buttonString, "STICK_") then
99 | local stickStart, stickEnd = string.find(buttonString, "STICK_")
100 | buttonString = string.sub(buttonString, stickEnd + 1, string.len(buttonString)) .. "_STICK"
101 | end
102 |
103 | buttonString = string.gsub(buttonString, "_", " ")
104 |
105 | InputHelper.ControllerToString[num] = buttonString
106 | end
107 |
108 | -------------------------
109 | --safe keyboard pressed--
110 | -------------------------
111 |
112 | --functions to use that work around a bug related to controller inputs
113 | function InputHelper.KeyboardTriggered(key, controllerIndex)
114 | return Input.IsButtonTriggered(key, controllerIndex) and not Input.IsButtonTriggered(key % 32, controllerIndex)
115 | end
116 |
117 | function InputHelper.KeyboardPressed(key, controllerIndex)
118 | return Input.IsButtonPressed(key, controllerIndex) and not Input.IsButtonPressed(key % 32, controllerIndex)
119 | end
120 |
121 | --------------------------
122 | --multiple button checks--
123 | --------------------------
124 |
125 | function InputHelper.MultipleActionTriggered(actions, controllerIndex)
126 | for i, action in pairs(actions) do
127 | for index = 0, 4 do
128 | if controllerIndex ~= nil then
129 | index = controllerIndex
130 | end
131 |
132 | if Input.IsActionTriggered(action, index) then
133 | return action
134 | end
135 |
136 | if controllerIndex ~= nil then
137 | break
138 | end
139 | end
140 | end
141 |
142 | return nil
143 | end
144 |
145 | function InputHelper.MultipleActionPressed(actions, controllerIndex)
146 | for i, action in pairs(actions) do
147 | for index = 0, 4 do
148 | if controllerIndex ~= nil then
149 | index = controllerIndex
150 | end
151 |
152 | if Input.IsActionPressed(action, index) then
153 | return action
154 | end
155 |
156 | if controllerIndex ~= nil then
157 | break
158 | end
159 | end
160 | end
161 |
162 | return nil
163 | end
164 |
165 | function InputHelper.MultipleButtonTriggered(buttons, controllerIndex)
166 | for i, button in pairs(buttons) do
167 | for index = 0, 4 do
168 | if controllerIndex ~= nil then
169 | index = controllerIndex
170 | end
171 |
172 | if Input.IsButtonTriggered(button, index) then
173 | return button
174 | end
175 |
176 | if controllerIndex ~= nil then
177 | break
178 | end
179 | end
180 | end
181 |
182 | return nil
183 | end
184 |
185 | function InputHelper.MultipleButtonPressed(buttons, controllerIndex)
186 | for i, button in pairs(buttons) do
187 | for index = 0, 4 do
188 | if controllerIndex ~= nil then
189 | index = controllerIndex
190 | end
191 |
192 | if Input.IsButtonPressed(button, index) then
193 | return button
194 | end
195 |
196 | if controllerIndex ~= nil then
197 | break
198 | end
199 | end
200 | end
201 |
202 | return nil
203 | end
204 |
205 | function InputHelper.MultipleKeyboardTriggered(keys, controllerIndex)
206 | for i, key in pairs(keys) do
207 | for index = 0, 4 do
208 | if controllerIndex ~= nil then
209 | index = controllerIndex
210 | end
211 |
212 | if InputHelper.KeyboardTriggered(key, index) then
213 | return key
214 | end
215 |
216 | if controllerIndex ~= nil then
217 | break
218 | end
219 | end
220 | end
221 |
222 | return nil
223 | end
224 |
225 | function InputHelper.MultipleKeyboardPressed(keys, controllerIndex)
226 | for i, key in pairs(keys) do
227 | for index = 0, 4 do
228 | if controllerIndex ~= nil then
229 | index = controllerIndex
230 | end
231 |
232 | if InputHelper.KeyboardPressed(key, index) then
233 | return key
234 | end
235 |
236 | if controllerIndex ~= nil then
237 | break
238 | end
239 | end
240 | end
241 |
242 | return nil
243 | end
244 |
245 | ---------------
246 | --force input--
247 | ---------------
248 |
249 | local forcingActionTriggered = {}
250 | function InputHelper.ForceActionTriggered(controllerIndex, buttonAction, value)
251 | forcingActionTriggered[controllerIndex] = forcingActionTriggered[controllerIndex] or {}
252 | forcingActionTriggered[controllerIndex][buttonAction] = value
253 | end
254 |
255 | local forcingActionPressed = {}
256 | local forcingActionPressedTimer = {}
257 | function InputHelper.ForceActionPressed(controllerIndex, buttonAction, value, timer)
258 | forcingActionPressed[controllerIndex] = forcingActionPressed[controllerIndex] or {}
259 | forcingActionPressed[controllerIndex][buttonAction] = value
260 |
261 | timer = timer or 1
262 | forcingActionPressedTimer[controllerIndex] = forcingActionPressedTimer[controllerIndex] or {}
263 | forcingActionPressedTimer[controllerIndex][buttonAction] = timer
264 | end
265 |
266 | function InputHelper.HandleForceActionPressed(_, entity, inputHook, buttonAction)
267 | if entity and entity:ToPlayer() then
268 | local player = entity:ToPlayer()
269 | local controllerIndex = player.ControllerIndex
270 |
271 | if inputHook == InputHook.IS_ACTION_TRIGGERED then
272 | if forcingActionTriggered[controllerIndex]
273 | and forcingActionTriggered[controllerIndex][buttonAction] ~= nil
274 | then
275 | local toReturn = forcingActionTriggered[controllerIndex][buttonAction]
276 | forcingActionTriggered[controllerIndex][buttonAction] = nil
277 |
278 | return toReturn
279 | end
280 | elseif inputHook == InputHook.IS_ACTION_PRESSED then
281 | if forcingActionPressed[controllerIndex] and forcingActionPressed[controllerIndex][buttonAction] ~= nil then
282 | local toReturn = forcingActionPressed[controllerIndex][buttonAction]
283 |
284 | forcingActionPressedTimer[controllerIndex][buttonAction] = forcingActionPressedTimer[controllerIndex][
285 | buttonAction]
286 | - 1
287 | if forcingActionPressedTimer[controllerIndex][buttonAction] <= 0 then
288 | forcingActionPressed[controllerIndex][buttonAction] = nil
289 | end
290 |
291 | return toReturn
292 | end
293 | end
294 | end
295 | end
296 |
297 | InputHelper.Mod:AddCallback(ModCallbacks.MC_INPUT_ACTION, InputHelper.HandleForceActionPressed)
298 |
299 | return InputHelper
300 |
--------------------------------------------------------------------------------
/scripts/modconfig.lua:
--------------------------------------------------------------------------------
1 | -- Imports
2 | local json = require("json")
3 |
4 | -------------
5 | -- version --
6 | -------------
7 |
8 | -- The final version of Chifilly's Mod Config Menu fork was 33.
9 | -- For the pure version, we arbitrarily selected a starting point of 100 and incremented from there.
10 | local VERSION = 112
11 | local IS_DEV = false
12 |
13 | ModConfigMenu = {}
14 | ModConfigMenu.Version = VERSION
15 |
16 | -------------------------
17 | --SAVE HELPER FUNCTIONS--
18 | -------------------------
19 |
20 | local SaveHelper = {}
21 |
22 | function SaveHelper.CopyTable(tableToCopy)
23 | local table2 = {}
24 |
25 | for i, value in pairs(tableToCopy) do
26 | if type(value) == "table" then
27 | table2[i] = SaveHelper.CopyTable(value)
28 | else
29 | table2[i] = value
30 | end
31 | end
32 |
33 | return table2
34 | end
35 |
36 | function SaveHelper.FillTable(tableToFill, tableToFillFrom)
37 | for i, value in pairs(tableToFillFrom) do
38 | if tableToFill[i] ~= nil then
39 | if type(value) == "table" then
40 | if type(tableToFill[i]) ~= "table" then
41 | tableToFill[i] = {}
42 | end
43 |
44 | tableToFill[i] = SaveHelper.FillTable(tableToFill[i], value)
45 | else
46 | tableToFill[i] = value
47 | end
48 | else
49 | if type(value) == "table" then
50 | if type(tableToFill[i]) ~= "table" then
51 | tableToFill[i] = {}
52 | end
53 |
54 | tableToFill[i] = SaveHelper.FillTable({}, value)
55 | else
56 | tableToFill[i] = value
57 | end
58 | end
59 | end
60 |
61 | return tableToFill
62 | end
63 |
64 | -----------
65 | -- setup --
66 | -----------
67 |
68 | Isaac.DebugString("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loading...")
69 |
70 | local vecZero = Vector(0, 0)
71 | local restartWarnMessage = nil
72 | local rerunWarnMessage = nil
73 |
74 | if not InputHelper then
75 | require("scripts.inputhelper")
76 | if not InputHelper then
77 | error("Mod Config Menu requires Input Helper to function", 2)
78 | end
79 | end
80 |
81 | if not ScreenHelper then
82 | require("scripts.screenhelper")
83 | if not ScreenHelper then
84 | error("Mod Config Menu requires Screen Helper to function", 2)
85 | end
86 | end
87 |
88 | ModConfigMenu.Mod = RegisterMod("Mod Config Menu", 1)
89 |
90 | ----------
91 | --SAVING--
92 | ----------
93 |
94 | ModConfigMenu.ConfigDefault = {}
95 | ModConfigMenu.Config = {}
96 |
97 | ---------------------------------
98 | -- Saving and loading MCM data --
99 | ---------------------------------
100 |
101 | local function save()
102 | local mod = ModConfigMenu.Mod
103 |
104 | if IS_DEV then
105 | Isaac.DebugString("MCM is saving data.")
106 | end
107 |
108 | local jsonString = json.encode(ModConfigMenu.Config)
109 | if jsonString == nil or jsonString == "" then
110 | return
111 | end
112 |
113 | mod:SaveData(jsonString)
114 | end
115 |
116 | local function load()
117 | local mod = ModConfigMenu.Mod
118 |
119 | if IS_DEV then
120 | Isaac.DebugString("MCM is loading data.")
121 | end
122 |
123 | if not mod:HasData() then
124 | save()
125 | return
126 | end
127 |
128 | local jsonString = mod:LoadData()
129 | if jsonString == nil or jsonString == "" or jsonString == "[]" or jsonString == "{}" then
130 | save()
131 | return
132 | end
133 |
134 | local data = nil
135 | local function loadJSON()
136 | data = json.decode(jsonString)
137 | end
138 |
139 | if not pcall(loadJSON) or data == nil then
140 | Isaac.DebugString("Error: Failed to load Mod Config Menu data from the \"save#.dat\" file.")
141 | return
142 | end
143 |
144 | ModConfigMenu.LoadSave(data)
145 | end
146 |
147 | function ModConfigMenu.LoadSave(data)
148 | local saveData = SaveHelper.CopyTable(ModConfigMenu.ConfigDefault)
149 | if type(data) == "table" then
150 | saveData["General"] = SaveHelper.FillTable(saveData["General"], data["General"] or {})
151 | saveData["Mod Config Menu"] = SaveHelper.FillTable(saveData["Mod Config Menu"], data["Mod Config Menu"] or data or {})
152 | if IS_DEV then
153 | Isaac.DebugString("Successfully loaded data from the \"save#.dat\" file.")
154 | end
155 | end
156 |
157 | local currentData = SaveHelper.CopyTable(ModConfigMenu.Config)
158 | saveData = SaveHelper.FillTable(currentData, saveData)
159 |
160 | ModConfigMenu.Config = SaveHelper.CopyTable(saveData)
161 |
162 | --make sure ScreenHelper's offset matches MCM's offset
163 | if ScreenHelper then
164 | ScreenHelper.SetOffset(ModConfigMenu.Config["General"].HudOffset)
165 | end
166 | end
167 |
168 | local function vanillaMCMOptionRegisterSave(mcmOption)
169 | local oldOnChange = mcmOption.OnChange
170 | mcmOption.OnChange = function(currentValue)
171 | local value = oldOnChange(currentValue)
172 | save()
173 | return value
174 | end
175 | end
176 |
177 | --------------
178 | --game start--
179 | --------------
180 | local versionPrintFont = Font()
181 | versionPrintFont:Load("font/pftempestasevencondensed.fnt")
182 |
183 | local versionPrintTimer = 0
184 | local isFirstRun = true
185 |
186 | --returns true if the room is clear and there are no active enemies and there are no projectiles
187 | ModConfigMenu.IgnoreActiveEnemies = ModConfigMenu.IgnoreActiveEnemies or {}
188 | function ModConfigMenu.RoomIsSafe()
189 | local roomHasDanger = false
190 |
191 | for _, entity in pairs(Isaac.GetRoomEntities()) do
192 | if (
193 | entity:IsActiveEnemy()
194 | and not entity:HasEntityFlags(EntityFlag.FLAG_FRIENDLY)
195 | and (
196 | not ModConfigMenu.IgnoreActiveEnemies[entity.Type]
197 | or (
198 | ModConfigMenu.IgnoreActiveEnemies[entity.Type]
199 | and not ModConfigMenu.IgnoreActiveEnemies[entity.Type][ -1]
200 | and not ModConfigMenu.IgnoreActiveEnemies[entity.Type][entity.Variant]
201 | )
202 | )
203 | ) then
204 | roomHasDanger = true
205 | elseif (
206 | entity.Type == EntityType.ENTITY_PROJECTILE
207 | and entity:ToProjectile().ProjectileFlags & ProjectileFlags.CANT_HIT_PLAYER ~= 1
208 | ) then
209 | roomHasDanger = true
210 | elseif entity.Type == EntityType.ENTITY_BOMBDROP then
211 | roomHasDanger = true
212 | end
213 | end
214 |
215 | local game = Game()
216 | local room = game:GetRoom()
217 |
218 | if room:IsClear() and not roomHasDanger then
219 | return true
220 | end
221 |
222 | return false
223 | end
224 |
225 | ModConfigMenu.IsVisible = false
226 | function ModConfigMenu.PostGameStarted()
227 | if IS_DEV then
228 | Isaac.DebugString("POST_GAME_STARTED")
229 | end
230 |
231 | rerunWarnMessage = nil
232 |
233 | load()
234 |
235 | if not isFirstRun then
236 | return
237 | end
238 | isFirstRun = false
239 |
240 | if ModConfigMenu.Config["Mod Config Menu"].ShowControls and isFirstRun then
241 | versionPrintTimer = 120
242 | end
243 |
244 | ModConfigMenu.IsVisible = false
245 |
246 | -- Add Potato Dummy to the ignore list.
247 | local potatoType = Isaac.GetEntityTypeByName("Potato Dummy")
248 | local potatoVariant = Isaac.GetEntityVariantByName("Potato Dummy")
249 |
250 | if potatoType and potatoVariant > 0 then
251 | ModConfigMenu.IgnoreActiveEnemies[potatoType] = ModConfigMenu.IgnoreActiveEnemies or {}
252 | ModConfigMenu.IgnoreActiveEnemies[potatoType][potatoVariant] = true
253 | end
254 | end
255 |
256 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, ModConfigMenu.PostGameStarted)
257 |
258 |
259 | ---------------
260 | --post update--
261 | ---------------
262 | function ModConfigMenu.PostUpdate()
263 | if versionPrintTimer > 0 then
264 | versionPrintTimer = versionPrintTimer - 1
265 | end
266 | end
267 |
268 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_UPDATE, ModConfigMenu.PostUpdate)
269 |
270 |
271 | ------------------------------------
272 | --set up the menu sprites and font--
273 | ------------------------------------
274 | function ModConfigMenu.GetMenuAnm2Sprite(animation, frame, color)
275 | local sprite = Sprite()
276 |
277 | sprite:Load("gfx/ui/modconfig/menu.anm2", true)
278 | sprite:SetFrame(animation or "Idle", frame or 0)
279 |
280 | if color then
281 | sprite.Color = color
282 | end
283 |
284 | return sprite
285 | end
286 |
287 | --main menu sprites
288 | local MenuSprite = ModConfigMenu.GetMenuAnm2Sprite("Idle", 0)
289 | local MenuOverlaySprite = ModConfigMenu.GetMenuAnm2Sprite("IdleOverlay", 0)
290 | local PopupSprite = ModConfigMenu.GetMenuAnm2Sprite("Popup", 0)
291 |
292 | --main cursors
293 | local CursorSpriteRight = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 0)
294 | local CursorSpriteUp = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 1)
295 | local CursorSpriteDown = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 2)
296 |
297 | --colors
298 | local colorDefault = Color(1, 1, 1, 1, 0, 0, 0)
299 | local colorHalf = Color(1, 1, 1, 0.5, 0, 0, 0)
300 |
301 | --subcategory pane cursors
302 | local SubcategoryCursorSpriteLeft = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 3, colorHalf)
303 | local SubcategoryCursorSpriteRight = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 0, colorHalf)
304 |
305 | --options pane cursors
306 | local OptionsCursorSpriteUp = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 1, colorHalf)
307 | local OptionsCursorSpriteDown = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 2, colorHalf)
308 |
309 | --other options pane objects
310 | local SubcategoryDividerSprite = ModConfigMenu.GetMenuAnm2Sprite("Divider", 0, colorHalf)
311 | local SliderSprite = ModConfigMenu.GetMenuAnm2Sprite("Slider1", 0)
312 |
313 | --strikeout
314 | local StrikeOutSprite = ModConfigMenu.GetMenuAnm2Sprite("Strikeout", 0)
315 |
316 | --back/select corner papers
317 | local CornerSelect = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 0)
318 | local CornerBack = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 1)
319 | local CornerOpen = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 2)
320 | local CornerExit = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 3)
321 |
322 | --fonts
323 | local Font10 = Font()
324 | Font10:Load("font/teammeatfont10.fnt")
325 |
326 | local Font12 = Font()
327 | Font12:Load("font/teammeatfont12.fnt")
328 |
329 | local Font16Bold = Font()
330 | Font16Bold:Load("font/teammeatfont16bold.fnt")
331 |
332 | --popups
333 | ModConfigMenu.PopupGfx = ModConfigMenu.PopupGfx or {}
334 | ModConfigMenu.PopupGfx.THIN_SMALL = "gfx/ui/modconfig/popup_thin_small.png"
335 | ModConfigMenu.PopupGfx.THIN_MEDIUM = "gfx/ui/modconfig/popup_thin_medium.png"
336 | ModConfigMenu.PopupGfx.THIN_LARGE = "gfx/ui/modconfig/popup_thin_large.png"
337 | ModConfigMenu.PopupGfx.WIDE_SMALL = "gfx/ui/modconfig/popup_wide_small.png"
338 | ModConfigMenu.PopupGfx.WIDE_MEDIUM = "gfx/ui/modconfig/popup_wide_medium.png"
339 | ModConfigMenu.PopupGfx.WIDE_LARGE = "gfx/ui/modconfig/popup_wide_large.png"
340 |
341 |
342 | -------------------------
343 | --add setting functions--
344 | -------------------------
345 | ModConfigMenu.OptionType = ModConfigMenu.OptionType or {}
346 | ModConfigMenu.OptionType.TEXT = 1
347 | ModConfigMenu.OptionType.SPACE = 2
348 | ModConfigMenu.OptionType.SCROLL = 3
349 | ModConfigMenu.OptionType.BOOLEAN = 4
350 | ModConfigMenu.OptionType.NUMBER = 5
351 | ModConfigMenu.OptionType.KEYBIND_KEYBOARD = 6
352 | ModConfigMenu.OptionType.KEYBIND_CONTROLLER = 7
353 | ModConfigMenu.OptionType.TITLE = 8
354 |
355 | ModConfigMenu.MenuData = ModConfigMenu.MenuData or {}
356 |
357 | --CATEGORY FUNCTIONS
358 | function ModConfigMenu.GetCategoryIDByName(categoryName)
359 | if type(categoryName) ~= "string" then
360 | return categoryName
361 | end
362 |
363 | local categoryID = nil
364 |
365 | for i = 1, #ModConfigMenu.MenuData do
366 | if categoryName == ModConfigMenu.MenuData[i].Name then
367 | categoryID = i
368 | break
369 | end
370 | end
371 |
372 | return categoryID
373 | end
374 |
375 | function ModConfigMenu.UpdateCategory(categoryName, dataTable)
376 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
377 | error("ModConfigMenu.UpdateCategory - No valid category name provided", 2)
378 | end
379 |
380 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
381 | if categoryID == nil then
382 | categoryID = #ModConfigMenu.MenuData + 1
383 | ModConfigMenu.MenuData[categoryID] = {}
384 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
385 | end
386 |
387 | if type(categoryName) == "string" or dataTable.Name then
388 | ModConfigMenu.MenuData[categoryID].Name = dataTable.Name or categoryName
389 | end
390 |
391 | if dataTable.Info then
392 | ModConfigMenu.MenuData[categoryID].Info = dataTable.Info
393 | end
394 |
395 | if dataTable.IsOld then
396 | ModConfigMenu.MenuData[categoryID].IsOld = dataTable.IsOld
397 | end
398 | end
399 |
400 | function ModConfigMenu.SetCategoryInfo(categoryName, info)
401 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
402 | error("ModConfigMenu.SetCategoryInfo - No valid category name provided", 2)
403 | end
404 |
405 | ModConfigMenu.UpdateCategory(categoryName, {
406 | Info = info
407 | })
408 | end
409 |
410 | function ModConfigMenu.RemoveCategory(categoryName)
411 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
412 | error("ModConfigMenu.RemoveCategory - No valid category name provided", 2)
413 | end
414 |
415 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
416 | if categoryID then
417 | table.remove(ModConfigMenu.MenuData, categoryID)
418 | return true
419 | end
420 |
421 | return false
422 | end
423 |
424 | --SUBCATEGORY FUNCTIONS
425 | function ModConfigMenu.GetSubcategoryIDByName(categoryName, subcategoryName)
426 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
427 | error("ModConfigMenu.GetSubcategoryIDByName - No valid category name provided", 2)
428 | end
429 |
430 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
431 |
432 | if type(subcategoryName) ~= "string" then
433 | return subcategoryName
434 | end
435 |
436 | local subcategoryID = nil
437 |
438 | for i = 1, #ModConfigMenu.MenuData[categoryID].Subcategories do
439 | if subcategoryName == ModConfigMenu.MenuData[categoryID].Subcategories[i].Name then
440 | subcategoryID = i
441 | break
442 | end
443 | end
444 |
445 | return subcategoryID
446 | end
447 |
448 | function ModConfigMenu.UpdateSubcategory(categoryName, subcategoryName, dataTable)
449 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
450 | error("ModConfigMenu.UpdateSubcategory - No valid category name provided", 2)
451 | end
452 |
453 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
454 | error("ModConfigMenu.UpdateSubcategory - No valid subcategory name provided", 2)
455 | end
456 |
457 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
458 | if categoryID == nil then
459 | categoryID = #ModConfigMenu.MenuData + 1
460 | ModConfigMenu.MenuData[categoryID] = {}
461 | ModConfigMenu.MenuData[categoryID].Name = tostring(categoryName)
462 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
463 | end
464 |
465 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
466 | if subcategoryID == nil then
467 | subcategoryID = #ModConfigMenu.MenuData[categoryID].Subcategories + 1
468 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID] = {}
469 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options = {}
470 | end
471 |
472 | if type(subcategoryName) == "string" or dataTable.Name then
473 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Name = dataTable.Name or subcategoryName
474 | end
475 |
476 | if dataTable.Info then
477 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Info = dataTable.Info
478 | end
479 | end
480 |
481 | function ModConfigMenu.RemoveSubcategory(categoryName, subcategoryName)
482 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
483 | error("ModConfigMenu.RemoveSubcategory - No valid category name provided", 2)
484 | end
485 |
486 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
487 | error("ModConfigMenu.RemoveSubcategory - No valid subcategory name provided", 2)
488 | end
489 |
490 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
491 | if categoryID then
492 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
493 | if subcategoryID then
494 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories, subcategoryID)
495 | return true
496 | end
497 | end
498 |
499 | return false
500 | end
501 |
502 | --SETTING FUNCTIONS
503 | function ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
504 | if settingTable == nil then
505 | settingTable = subcategoryName
506 | subcategoryName = nil
507 | end
508 |
509 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
510 | error("ModConfigMenu.AddSetting - No valid category name provided", 2)
511 | end
512 |
513 | subcategoryName = subcategoryName or "Uncategorized"
514 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
515 | error("ModConfigMenu.AddSetting - No valid subcategory name provided", 2)
516 | end
517 |
518 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
519 | if categoryID == nil then
520 | categoryID = #ModConfigMenu.MenuData + 1
521 | ModConfigMenu.MenuData[categoryID] = {}
522 | ModConfigMenu.MenuData[categoryID].Name = tostring(categoryName)
523 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
524 | end
525 |
526 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
527 | if subcategoryID == nil then
528 | subcategoryID = #ModConfigMenu.MenuData[categoryID].Subcategories + 1
529 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID] = {}
530 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Name = tostring(subcategoryName)
531 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options = {}
532 | end
533 |
534 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[
535 | #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options + 1] = settingTable
536 |
537 | return settingTable
538 | end
539 |
540 | function ModConfigMenu.AddText(categoryName, subcategoryName, text, color)
541 | if color == nil and type(text) ~= "string" and type(text) ~= "function" then
542 | color = text
543 | text = subcategoryName
544 | subcategoryName = nil
545 | end
546 |
547 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
548 | error("ModConfigMenu.AddText - No valid category name provided", 2)
549 | end
550 |
551 | subcategoryName = subcategoryName or "Uncategorized"
552 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
553 | error("ModConfigMenu.AddText - No valid subcategory name provided", 2)
554 | end
555 |
556 | local settingTable = {
557 | Type = ModConfigMenu.OptionType.TEXT,
558 | Display = text,
559 | Color = color,
560 | NoCursorHere = true
561 | }
562 |
563 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
564 | end
565 |
566 | function ModConfigMenu.AddTitle(categoryName, subcategoryName, text, color)
567 | if color == nil and type(text) ~= "string" and type(text) ~= "function" then
568 | color = text
569 | text = subcategoryName
570 | subcategoryName = nil
571 | end
572 |
573 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
574 | error("ModConfigMenu.AddTitle - No valid category name provided", 2)
575 | end
576 |
577 | subcategoryName = subcategoryName or "Uncategorized"
578 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
579 | error("ModConfigMenu.AddTitle - No valid subcategory name provided", 2)
580 | end
581 |
582 | local settingTable = {
583 | Type = ModConfigMenu.OptionType.TITLE,
584 | Display = text,
585 | Color = color,
586 | NoCursorHere = true
587 | }
588 |
589 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
590 | end
591 |
592 | function ModConfigMenu.AddSpace(categoryName, subcategoryName)
593 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
594 | error("ModConfigMenu.AddSpace - No valid category name provided", 2)
595 | end
596 |
597 | subcategoryName = subcategoryName or "Uncategorized"
598 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
599 | error("ModConfigMenu.AddSpace - No valid subcategory name provided", 2)
600 | end
601 |
602 | local settingTable = {
603 | Type = ModConfigMenu.OptionType.SPACE
604 | }
605 |
606 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
607 | end
608 |
609 | local altSlider = false
610 | function ModConfigMenu.SimpleAddSetting(settingType, categoryName, subcategoryName, configTableAttribute, minValue,
611 | maxValue, modifyBy, defaultValue, displayText, displayValueProxies, displayDevice
612 | , info, color, functionName)
613 | --set default values
614 | if defaultValue == nil then
615 | if settingType == ModConfigMenu.OptionType.BOOLEAN then
616 | defaultValue = false
617 | else
618 | defaultValue = 0
619 | end
620 | end
621 |
622 | if settingType == ModConfigMenu.OptionType.NUMBER then
623 | minValue = minValue or 0
624 | maxValue = maxValue or 10
625 | modifyBy = modifyBy or 1
626 | else
627 | minValue = nil
628 | maxValue = nil
629 | modifyBy = nil
630 | end
631 |
632 | functionName = functionName or "SimpleAddSetting"
633 |
634 | --erroring
635 | if categoryName == nil then
636 | error("ModConfigMenu." .. tostring(functionName) .. " - No valid category name provided", 2)
637 | end
638 | if configTableAttribute == nil then
639 | error("ModConfigMenu." .. tostring(functionName) .. " - No valid config table attribute provided", 2)
640 | end
641 |
642 | --create config value
643 | ModConfigMenu.Config[categoryName] = ModConfigMenu.Config[categoryName] or {}
644 | if ModConfigMenu.Config[categoryName][configTableAttribute] == nil then
645 | ModConfigMenu.Config[categoryName][configTableAttribute] = defaultValue
646 | end
647 |
648 | ModConfigMenu.ConfigDefault[categoryName] = ModConfigMenu.ConfigDefault[categoryName] or {}
649 | if ModConfigMenu.ConfigDefault[categoryName][configTableAttribute] == nil then
650 | ModConfigMenu.ConfigDefault[categoryName][configTableAttribute] = defaultValue
651 | end
652 |
653 | --setting
654 | local settingTable = {
655 | Type = settingType,
656 | Attribute = configTableAttribute,
657 | CurrentSetting = function()
658 | return ModConfigMenu.Config[categoryName][configTableAttribute]
659 | end,
660 | Default = defaultValue,
661 | Display = function(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
662 | local currentValue = ModConfigMenu.Config[categoryName][configTableAttribute]
663 |
664 | local displayString = ""
665 |
666 | if displayText then
667 | displayString = displayText .. ": "
668 | end
669 |
670 | if settingType == ModConfigMenu.OptionType.SCROLL then
671 | displayString = displayString .. "$scroll" .. tostring(math.floor(currentValue))
672 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
673 | local key = "None"
674 |
675 | if currentValue > -1 then
676 | key = "Unknown Key"
677 |
678 | if InputHelper.KeyboardToString[currentValue] then
679 | key = InputHelper.KeyboardToString[currentValue]
680 | end
681 | end
682 |
683 | displayString = displayString .. key
684 |
685 | if displayDevice then
686 | displayString = displayString .. " (keyboard)"
687 | end
688 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
689 | local key = "None"
690 |
691 | if currentValue > -1 then
692 | key = "Unknown Button"
693 |
694 | if InputHelper.ControllerToString[currentValue] then
695 | key = InputHelper.ControllerToString[currentValue]
696 | end
697 | end
698 |
699 | displayString = displayString .. key
700 |
701 | if displayDevice then
702 | displayString = displayString .. " (controller)"
703 | end
704 | elseif displayValueProxies and displayValueProxies[currentValue] then
705 | displayString = displayString .. tostring(displayValueProxies[currentValue])
706 | else
707 | displayString = displayString .. tostring(currentValue)
708 | end
709 |
710 | return displayString
711 | end,
712 | OnChange = function(currentValue)
713 | if not currentValue then
714 | if settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
715 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
716 | currentValue = -1
717 | end
718 | end
719 |
720 | ModConfigMenu.Config[categoryName][configTableAttribute] = currentValue
721 | end,
722 | Info = info,
723 | Color = color
724 | }
725 |
726 | if settingType == ModConfigMenu.OptionType.NUMBER then
727 | settingTable.Minimum = minValue
728 | settingTable.Maximum = maxValue
729 | settingTable.ModifyBy = modifyBy
730 | elseif settingType == ModConfigMenu.OptionType.SCROLL then
731 | settingTable.AltSlider = altSlider
732 | altSlider = not altSlider
733 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
734 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
735 | settingTable.PopupGfx = ModConfigMenu.PopupGfx.WIDE_SMALL
736 | settingTable.PopupWidth = 280
737 | settingTable.Popup = function()
738 | local currentValue = ModConfigMenu.Config[categoryName][configTableAttribute]
739 |
740 | local goBackString = "back"
741 | if ModConfigMenu.Config.LastBackPressed then
742 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed] then
743 | goBackString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed]
744 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed] then
745 | goBackString = InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed]
746 | end
747 | end
748 |
749 | local keepSettingString = ""
750 | if currentValue > -1 then
751 | local currentSettingString = nil
752 | if (settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD and InputHelper.KeyboardToString[currentValue]) then
753 | currentSettingString = InputHelper.KeyboardToString[currentValue]
754 | elseif (
755 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER and InputHelper.ControllerToString[currentValue]) then
756 | currentSettingString = InputHelper.ControllerToString[currentValue]
757 | end
758 |
759 | keepSettingString = "This setting is currently set to \"" ..
760 | currentSettingString .. "\".$newlinePress this button to keep it unchanged.$newline$newline"
761 | end
762 |
763 | local deviceString = ""
764 | if settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
765 | deviceString = "keyboard"
766 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
767 | deviceString = "controller"
768 | end
769 |
770 | return "Press a button on your " ..
771 | deviceString ..
772 | " to change this setting.$newline$newline" ..
773 | keepSettingString .. "Press \"" .. goBackString .. "\" to go back and clear this setting."
774 | end
775 | end
776 |
777 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
778 | end
779 |
780 | function ModConfigMenu.AddBooleanSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText,
781 | displayValueProxies, info, color)
782 | --move args around
783 | if type(configTableAttribute) ~= "string" then
784 | color = info
785 | info = displayValueProxies
786 | displayValueProxies = displayText
787 | displayText = defaultValue
788 | defaultValue = configTableAttribute
789 | configTableAttribute = subcategoryName
790 | subcategoryName = nil
791 | end
792 |
793 | if type(defaultValue) ~= "boolean" then
794 | color = info
795 | info = displayValueProxies
796 | displayValueProxies = displayText
797 | displayText = defaultValue
798 | defaultValue = false
799 | end
800 |
801 | if type(displayValueProxies) ~= "table" or type(info) == "userdata" or type(info) == "nil" then
802 | color = info
803 | info = displayValueProxies
804 | displayValueProxies = nil
805 | end
806 |
807 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.BOOLEAN, categoryName, subcategoryName,
808 | configTableAttribute, nil, nil, nil, defaultValue, displayText, displayValueProxies, nil, info, color,
809 | "AddBooleanSetting")
810 | end
811 |
812 | function ModConfigMenu.AddNumberSetting(categoryName, subcategoryName, configTableAttribute, minValue, maxValue, modifyBy
813 | , defaultValue, displayText, displayValueProxies, info, color)
814 | --move args around
815 | if type(configTableAttribute) ~= "string" then
816 | color = info
817 | info = displayValueProxies
818 | displayValueProxies = displayText
819 | displayText = defaultValue
820 | defaultValue = modifyBy
821 | modifyBy = maxValue
822 | maxValue = minValue
823 | minValue = configTableAttribute
824 | configTableAttribute = subcategoryName
825 | subcategoryName = nil
826 | end
827 |
828 | if type(defaultValue) == "string" then
829 | color = info
830 | info = displayValueProxies
831 | displayValueProxies = displayText
832 | displayText = defaultValue
833 | defaultValue = modifyBy
834 | modifyBy = nil
835 | end
836 |
837 | if type(displayValueProxies) ~= "table" or type(info) == "userdata" or type(info) == "nil" then
838 | color = info
839 | info = displayValueProxies
840 | displayValueProxies = nil
841 | end
842 |
843 | --set default values
844 | defaultValue = defaultValue or 0
845 | minValue = minValue or 0
846 | maxValue = maxValue or 10
847 | modifyBy = modifyBy or 1
848 |
849 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.NUMBER, categoryName, subcategoryName,
850 | configTableAttribute, minValue, maxValue, modifyBy, defaultValue, displayText, displayValueProxies, nil, info,
851 | color
852 | ,
853 | "AddNumberSetting")
854 | end
855 |
856 | function ModConfigMenu.AddScrollSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText,
857 | info, color)
858 | --move args around
859 | if type(configTableAttribute) ~= "string" then
860 | color = info
861 | info = displayText
862 | displayText = defaultValue
863 | defaultValue = configTableAttribute
864 | configTableAttribute = subcategoryName
865 | subcategoryName = nil
866 | end
867 |
868 | if type(defaultValue) ~= "number" then
869 | color = info
870 | info = displayText
871 | displayText = defaultValue
872 | defaultValue = nil
873 | end
874 |
875 | --set default values
876 | defaultValue = defaultValue or 0
877 |
878 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.SCROLL, categoryName, subcategoryName,
879 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, nil, info, color, "AddScrollSetting")
880 | end
881 |
882 | function ModConfigMenu.AddKeyboardSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText
883 | , displayDevice, info, color)
884 | --move args around
885 | if type(configTableAttribute) ~= "string" then
886 | color = info
887 | info = displayDevice
888 | displayDevice = displayText
889 | displayText = defaultValue
890 | defaultValue = configTableAttribute
891 | configTableAttribute = subcategoryName
892 | subcategoryName = nil
893 | end
894 |
895 | if type(defaultValue) ~= "number" then
896 | color = info
897 | info = displayText
898 | displayText = defaultValue
899 | defaultValue = nil
900 | end
901 |
902 | if type(displayDevice) ~= "boolean" then
903 | color = info
904 | info = displayDevice
905 | displayDevice = false
906 | end
907 |
908 | --set default values
909 | defaultValue = defaultValue or -1
910 |
911 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.KEYBIND_KEYBOARD, categoryName, subcategoryName,
912 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, displayDevice, info, color,
913 | "AddKeyboardSetting")
914 | end
915 |
916 | function ModConfigMenu.AddControllerSetting(categoryName, subcategoryName, configTableAttribute, defaultValue,
917 | displayText, displayDevice, info, color)
918 | --move args around
919 | if type(configTableAttribute) ~= "string" then
920 | color = info
921 | info = displayDevice
922 | displayDevice = displayText
923 | displayText = defaultValue
924 | defaultValue = configTableAttribute
925 | configTableAttribute = subcategoryName
926 | subcategoryName = nil
927 | end
928 |
929 | if type(defaultValue) ~= "number" then
930 | color = info
931 | info = displayText
932 | displayText = defaultValue
933 | defaultValue = nil
934 | end
935 |
936 | if type(displayDevice) ~= "boolean" then
937 | color = info
938 | info = displayDevice
939 | displayDevice = false
940 | end
941 |
942 | --set default values
943 | defaultValue = defaultValue or -1
944 |
945 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.KEYBIND_CONTROLLER, categoryName, subcategoryName,
946 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, displayDevice, info, color,
947 | "AddControllerSetting")
948 | end
949 |
950 | function ModConfigMenu.RemoveSetting(categoryName, subcategoryName, settingAttribute)
951 | if settingAttribute == nil then
952 | settingAttribute = subcategoryName
953 | subcategoryName = nil
954 | end
955 |
956 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
957 | error("ModConfigMenu.RemoveSetting - No valid category name provided", 2)
958 | end
959 |
960 | subcategoryName = subcategoryName or "Uncategorized"
961 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
962 | error("ModConfigMenu.RemoveSetting - No valid subcategory name provided", 2)
963 | end
964 |
965 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
966 | if categoryID then
967 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
968 | if subcategoryID then
969 | --loop to find matching attribute
970 | for i = #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, 1, -1 do
971 | if ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i]
972 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Attribute
973 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Attribute == settingAttribute then
974 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, i)
975 | return true
976 | end
977 | end
978 |
979 | --loop to find matching display
980 | for i = #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, 1, -1 do
981 | if ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i]
982 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Display
983 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Display == settingAttribute then
984 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, i)
985 | return true
986 | end
987 | end
988 | end
989 | end
990 |
991 | return false
992 | end
993 |
994 | --------------------------
995 | --GENERAL SETTINGS SETUP--
996 | --------------------------
997 | ModConfigMenu.SetCategoryInfo("General", "Settings that affect the majority of mods")
998 |
999 | ----------------------
1000 | --HUD OFFSET SETTING--
1001 | ----------------------
1002 | local hudOffsetSetting = ModConfigMenu.AddScrollSetting(
1003 | "General", --category
1004 | "HudOffset", --attribute in table
1005 | Options and Options.HUDOffset * 10 or 0, --default value
1006 | "Hud Offset", --display text
1007 | "How far from the corners of the screen custom hud elements will be.$newlineTry to make this match your base-game setting."
1008 | )
1009 |
1010 | hudOffsetSetting.HideControls = true -- hide controls so the screen corner graphics are easier to see
1011 | hudOffsetSetting.ShowOffset = true -- shows screen offset
1012 | vanillaMCMOptionRegisterSave(hudOffsetSetting)
1013 |
1014 | --set up callback
1015 | local oldHudOffsetOnChange = hudOffsetSetting.OnChange
1016 | hudOffsetSetting.OnChange = function(currentValue)
1017 | --update screenhelper's offset
1018 | if ScreenHelper then
1019 | ScreenHelper.SetOffset(currentValue)
1020 | end
1021 |
1022 | return oldHudOffsetOnChange(currentValue)
1023 | end
1024 |
1025 | --------------------
1026 | --OVERLAYS SETTING--
1027 | --------------------
1028 | local overlays = ModConfigMenu.AddBooleanSetting(
1029 | "General", --category
1030 | "Overlays", --attribute in table
1031 | true, --default value
1032 | "Overlays", --display text
1033 | {
1034 | --value display text
1035 | [true] = "On",
1036 | [false] = "Off"
1037 | },
1038 | "Enable or disable custom visual overlays, like screen-wide fog."
1039 | )
1040 | vanillaMCMOptionRegisterSave(overlays)
1041 |
1042 | -----------------------
1043 | --CHARGE BARS SETTING--
1044 | -----------------------
1045 | local chargeBars = ModConfigMenu.AddBooleanSetting(
1046 | "General", --category
1047 | "ChargeBars", --attribute in table
1048 | false, --default value
1049 | "Charge Bars", --display text
1050 | {
1051 | --value display text
1052 | [true] = "On",
1053 | [false] = "Off"
1054 | },
1055 | "Enable or disable custom charge bar visuals for mod effects, like those from chargeable items."
1056 | )
1057 | vanillaMCMOptionRegisterSave(chargeBars)
1058 |
1059 | ---------------------
1060 | --BIG BOOKS SETTING--
1061 | ---------------------
1062 | local bigBooks = ModConfigMenu.AddBooleanSetting(
1063 | "General", --category
1064 | "BigBooks", --attribute in table
1065 | true, --default value
1066 | "Bigbooks", --display text
1067 | {
1068 | --value display text
1069 | [true] = "On",
1070 | [false] = "Off"
1071 | },
1072 | "Enable or disable custom big-book overlays which can appear when an active item is used."
1073 | )
1074 | vanillaMCMOptionRegisterSave(bigBooks)
1075 |
1076 | ---------------------
1077 | --ANNOUNCER SETTING--
1078 | ---------------------
1079 |
1080 | local announcer = ModConfigMenu.AddNumberSetting(
1081 | "General", --category
1082 | "Announcer", --attribute in table
1083 | 0, --minimum value
1084 | 2, --max value
1085 | 0, --default value,
1086 | "Announcer", --display text
1087 | {
1088 | --value display text
1089 | [0] = "Sometimes",
1090 | [1] = "Never",
1091 | [2] = "Always"
1092 | },
1093 | "Choose how often a voice-over will play when a pocket item (pill or card) is used."
1094 | )
1095 | vanillaMCMOptionRegisterSave(announcer)
1096 |
1097 | --------------------------
1098 | --GENERAL SETTINGS CLOSE--
1099 | --------------------------
1100 |
1101 | ModConfigMenu.AddSpace("General") --SPACE
1102 |
1103 | ModConfigMenu.AddText("General", "These settings apply to")
1104 | ModConfigMenu.AddText("General", "all mods which support them")
1105 |
1106 | ----------------------------------
1107 | --MOD CONFIG MENU SETTINGS SETUP--
1108 | ----------------------------------
1109 |
1110 | ModConfigMenu.SetCategoryInfo("Mod Config Menu",
1111 | "Settings specific to Mod Config Menu.$newlineChange keybindings for the menu here.")
1112 |
1113 | ModConfigMenu.AddTitle("Mod Config Menu", "Version " .. tostring(ModConfigMenu.Version) .. " !") --VERSION INDICATOR
1114 |
1115 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1116 |
1117 | ----------------------
1118 | --OPEN MENU KEYBOARD--
1119 | ----------------------
1120 |
1121 | local openMenuKeyboardSetting = ModConfigMenu.AddKeyboardSetting(
1122 | "Mod Config Menu", --category
1123 | "OpenMenuKeyboard", --attribute in table
1124 | Keyboard.KEY_L, --default value
1125 | "Open Menu", --display text
1126 | true, --if (keyboard) is displayed after the key text
1127 | "Choose what button on your keyboard will open Mod Config Menu."
1128 | )
1129 |
1130 | openMenuKeyboardSetting.IsOpenMenuKeybind = true
1131 | vanillaMCMOptionRegisterSave(openMenuKeyboardSetting)
1132 |
1133 | ------------------------
1134 | --OPEN MENU CONTROLLER--
1135 | ------------------------
1136 |
1137 | local openMenuControllerSetting = ModConfigMenu.AddControllerSetting(
1138 | "Mod Config Menu", --category
1139 | "OpenMenuController", --attribute in table
1140 | Controller.STICK_RIGHT, --default value
1141 | "Open Menu", --display text
1142 | true, --if (controller) is displayed after the key text
1143 | "Choose what button on your controller will open Mod Config Menu."
1144 | )
1145 | openMenuControllerSetting.IsOpenMenuKeybind = true
1146 | vanillaMCMOptionRegisterSave(openMenuControllerSetting)
1147 |
1148 | --f10 note
1149 | ModConfigMenu.AddText("Mod Config Menu", "F10 will always open this menu.")
1150 |
1151 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1152 |
1153 | ------------
1154 | --HIDE HUD--
1155 | ------------
1156 |
1157 | local hideHudSetting = ModConfigMenu.AddBooleanSetting(
1158 | "Mod Config Menu", --category
1159 | "HideHudInMenu", --attribute in table
1160 | true, --default value
1161 | "Hide HUD", --display text
1162 | {
1163 | --value display text
1164 | [true] = "Yes",
1165 | [false] = "No"
1166 | },
1167 | "Enable or disable the hud when this menu is open."
1168 | )
1169 | vanillaMCMOptionRegisterSave(hideHudSetting)
1170 |
1171 | --actively modify the hud visibility as this setting changes
1172 | local oldHideHudOnChange = hideHudSetting.OnChange
1173 | hideHudSetting.OnChange = function(currentValue)
1174 | oldHideHudOnChange(currentValue)
1175 |
1176 | local game = Game()
1177 | if REPENTANCE then
1178 | local hud = game:GetHUD()
1179 |
1180 | if currentValue then
1181 | if hud:IsVisible() then
1182 | hud:SetVisible(false)
1183 | end
1184 | else
1185 | if not hud:IsVisible() then
1186 | hud:SetVisible(true)
1187 | end
1188 | end
1189 | else
1190 | local seeds = game:GetSeeds()
1191 |
1192 | if currentValue then
1193 | if not seeds:HasSeedEffect(SeedEffect.SEED_NO_HUD) then
1194 | seeds:AddSeedEffect(SeedEffect.SEED_NO_HUD)
1195 | end
1196 | else
1197 | if seeds:HasSeedEffect(SeedEffect.SEED_NO_HUD) then
1198 | seeds:RemoveSeedEffect(SeedEffect.SEED_NO_HUD)
1199 | end
1200 | end
1201 | end
1202 | end
1203 |
1204 | ----------------------------
1205 | --RESET TO DEFAULT KEYBIND--
1206 | ----------------------------
1207 |
1208 | local resetKeybindSetting = ModConfigMenu.AddKeyboardSetting(
1209 | "Mod Config Menu", --category
1210 | "ResetToDefault", --attribute in table
1211 | Keyboard.KEY_F11, --default value
1212 | "Reset To Default Keybind", --display text
1213 | "Press this button on your keyboard to reset a setting to its default value."
1214 | )
1215 | resetKeybindSetting.IsResetKeybind = true
1216 | vanillaMCMOptionRegisterSave(resetKeybindSetting)
1217 |
1218 | -----------------
1219 | --SHOW CONTROLS--
1220 | -----------------
1221 |
1222 | local showControls = ModConfigMenu.AddBooleanSetting(
1223 | "Mod Config Menu", --category
1224 | "ShowControls", --attribute in table
1225 | true, --default value
1226 | "Show Controls", --display text
1227 | {
1228 | --value display text
1229 | [true] = "Yes",
1230 | [false] = "No"
1231 | },
1232 | "Disable this to remove the back and select widgets at the lower corners of the screen and remove the bottom start-up message."
1233 | )
1234 | vanillaMCMOptionRegisterSave(showControls)
1235 |
1236 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1237 |
1238 | -----------------
1239 | --COMPATIBILITY--
1240 | -----------------
1241 |
1242 | local compatibilitySetting = ModConfigMenu.AddBooleanSetting(
1243 | "Mod Config Menu", --category
1244 | "CompatibilityLayer", --attribute in table
1245 | false, --default value
1246 | "Disable Legacy Warnings", --display text
1247 | {
1248 | --value display text
1249 | [true] = "Yes",
1250 | [false] = "No"
1251 | },
1252 | "Use this setting to prevent warnings from being printed to the console for mods that use outdated features of Mod Config Menu."
1253 | )
1254 | vanillaMCMOptionRegisterSave(compatibilitySetting)
1255 |
1256 | local configMenuSubcategoriesCanShow = 3
1257 |
1258 | local configMenuInSubcategory = false
1259 | local configMenuInOptions = false
1260 | local configMenuInPopup = false
1261 |
1262 | local holdingCounterDown = 0
1263 | local holdingCounterUp = 0
1264 | local holdingCounterRight = 0
1265 | local holdingCounterLeft = 0
1266 |
1267 | local configMenuPositionCursorCategory = 1
1268 | local configMenuPositionCursorSubcategory = 1
1269 | local configMenuPositionCursorOption = 1
1270 |
1271 | local configMenuPositionFirstSubcategory = 1
1272 |
1273 | --valid action presses
1274 | local actionsDown = { ButtonAction.ACTION_DOWN, ButtonAction.ACTION_SHOOTDOWN, ButtonAction.ACTION_MENUDOWN }
1275 | local actionsUp = { ButtonAction.ACTION_UP, ButtonAction.ACTION_SHOOTUP, ButtonAction.ACTION_MENUUP }
1276 | local actionsRight = { ButtonAction.ACTION_RIGHT, ButtonAction.ACTION_SHOOTRIGHT, ButtonAction.ACTION_MENURIGHT }
1277 | local actionsLeft = { ButtonAction.ACTION_LEFT, ButtonAction.ACTION_SHOOTLEFT, ButtonAction.ACTION_MENULEFT }
1278 | local actionsBack = { ButtonAction.ACTION_PILLCARD, ButtonAction.ACTION_MAP, ButtonAction.ACTION_MENUBACK }
1279 | local actionsSelect = { ButtonAction.ACTION_ITEM, ButtonAction.ACTION_PAUSE, ButtonAction.ACTION_MENUCONFIRM,
1280 | ButtonAction.ACTION_BOMB }
1281 |
1282 | --ignore these buttons for the above actions
1283 | local ignoreActionButtons = { Controller.BUTTON_A, Controller.BUTTON_B, Controller.BUTTON_X, Controller.BUTTON_Y }
1284 |
1285 | local currentMenuCategory = nil
1286 | local currentMenuSubcategory = nil
1287 | local currentMenuOption = nil
1288 | local function updateCurrentMenuVars()
1289 | if ModConfigMenu.MenuData[configMenuPositionCursorCategory] then
1290 | currentMenuCategory = ModConfigMenu.MenuData[configMenuPositionCursorCategory]
1291 | if currentMenuCategory.Subcategories and currentMenuCategory.Subcategories[configMenuPositionCursorSubcategory] then
1292 | currentMenuSubcategory = currentMenuCategory.Subcategories[configMenuPositionCursorSubcategory]
1293 | if currentMenuSubcategory.Options and currentMenuSubcategory.Options[configMenuPositionCursorOption] then
1294 | currentMenuOption = currentMenuSubcategory.Options[configMenuPositionCursorOption]
1295 | end
1296 | end
1297 | end
1298 | end
1299 |
1300 | --leaving/entering menu sections
1301 | function ModConfigMenu.EnterPopup()
1302 | if configMenuInSubcategory and configMenuInOptions and not configMenuInPopup then
1303 | local foundValidPopup = false
1304 | if currentMenuOption
1305 | and currentMenuOption.Type
1306 | and currentMenuOption.Type ~= ModConfigMenu.OptionType.SPACE
1307 | and (currentMenuOption.Popup or currentMenuOption.Restart or currentMenuOption.Rerun) then
1308 | foundValidPopup = true
1309 | end
1310 | if foundValidPopup then
1311 | local popupSpritesheet = ModConfigMenu.PopupGfx.THIN_SMALL
1312 | if currentMenuOption.PopupGfx and type(currentMenuOption.PopupGfx) == "string" then
1313 | popupSpritesheet = currentMenuOption.PopupGfx
1314 | end
1315 | PopupSprite:ReplaceSpritesheet(5, popupSpritesheet)
1316 | PopupSprite:LoadGraphics()
1317 | configMenuInPopup = true
1318 | end
1319 | end
1320 | end
1321 |
1322 | function ModConfigMenu.EnterOptions()
1323 | if configMenuInSubcategory and not configMenuInOptions then
1324 | if currentMenuSubcategory
1325 | and currentMenuSubcategory.Options
1326 | and #currentMenuSubcategory.Options > 0 then
1327 | for optionIndex = 1, #currentMenuSubcategory.Options do
1328 | local thisOption = currentMenuSubcategory.Options[optionIndex]
1329 |
1330 | if thisOption.Type
1331 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
1332 | and
1333 | (
1334 | not thisOption.NoCursorHere or
1335 | (type(thisOption.NoCursorHere) == "function" and not thisOption.NoCursorHere()))
1336 | and thisOption.Display then
1337 | configMenuPositionCursorOption = optionIndex
1338 | configMenuInOptions = true
1339 | OptionsCursorSpriteUp.Color = colorDefault
1340 | OptionsCursorSpriteDown.Color = colorDefault
1341 |
1342 | break
1343 | end
1344 | end
1345 | end
1346 | end
1347 | end
1348 |
1349 | function ModConfigMenu.EnterSubcategory()
1350 | if not configMenuInSubcategory then
1351 | configMenuInSubcategory = true
1352 | SubcategoryCursorSpriteLeft.Color = colorDefault
1353 | SubcategoryCursorSpriteRight.Color = colorDefault
1354 | SubcategoryDividerSprite.Color = colorDefault
1355 |
1356 | local hasUsableCategories = false
1357 | if currentMenuCategory.Subcategories then
1358 | for j = 1, #currentMenuCategory.Subcategories do
1359 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
1360 | hasUsableCategories = true
1361 | end
1362 | end
1363 | end
1364 |
1365 | if not hasUsableCategories then
1366 | ModConfigMenu.EnterOptions()
1367 | end
1368 | end
1369 | end
1370 |
1371 | function ModConfigMenu.LeavePopup()
1372 | if configMenuInSubcategory and configMenuInOptions and configMenuInPopup then
1373 | if currentMenuOption then
1374 | if currentMenuOption.Restart then
1375 | restartWarnMessage = "One or more settings require you to restart the game"
1376 | elseif currentMenuOption.Rerun then
1377 | rerunWarnMessage = "One or more settings require you to start a new run"
1378 | end
1379 | end
1380 |
1381 | configMenuInPopup = false
1382 | end
1383 | end
1384 |
1385 | function ModConfigMenu.LeaveOptions()
1386 | if configMenuInSubcategory and configMenuInOptions then
1387 | configMenuInOptions = false
1388 | OptionsCursorSpriteUp.Color = colorHalf
1389 | OptionsCursorSpriteDown.Color = colorHalf
1390 |
1391 | local hasUsableCategories = false
1392 | if currentMenuCategory.Subcategories then
1393 | for j = 1, #currentMenuCategory.Subcategories do
1394 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
1395 | hasUsableCategories = true
1396 | end
1397 | end
1398 | end
1399 |
1400 | if not hasUsableCategories then
1401 | ModConfigMenu.LeaveSubcategory()
1402 | end
1403 | end
1404 | end
1405 |
1406 | function ModConfigMenu.LeaveSubcategory()
1407 | if configMenuInSubcategory then
1408 | configMenuInSubcategory = false
1409 | SubcategoryCursorSpriteLeft.Color = colorHalf
1410 | SubcategoryCursorSpriteRight.Color = colorHalf
1411 | SubcategoryDividerSprite.Color = colorHalf
1412 | end
1413 | end
1414 |
1415 | local mainSpriteColor = colorDefault
1416 | local optionsSpriteColor = colorDefault
1417 | local optionsSpriteColorAlpha = colorHalf
1418 | local mainFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1419 | local leftFontColor = KColor(35 / 255, 31 / 255, 30 / 255, 1)
1420 | local leftFontColorSelected = KColor(35 / 255, 50 / 255, 70 / 255, 1)
1421 |
1422 | local optionsFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1423 | local optionsFontColorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.5)
1424 | local optionsFontColorNoCursor = KColor(34 / 255, 32 / 255, 30 / 255, 0.8)
1425 | local optionsFontColorNoCursorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.4)
1426 | local optionsFontColorTitle = KColor(50 / 255, 0, 0, 1)
1427 | local optionsFontColorTitleAlpha = KColor(50 / 255, 0, 0, 0.5)
1428 |
1429 | local subcategoryFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1430 | local subcategoryFontColorSelected = KColor(34 / 255, 50 / 255, 70 / 255, 1)
1431 | local subcategoryFontColorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.5)
1432 | local subcategoryFontColorSelectedAlpha = KColor(34 / 255, 50 / 255, 70 / 255, 0.5)
1433 |
1434 | function ModConfigMenu.ConvertDisplayToTextTable(displayValue, lineWidth, font)
1435 | lineWidth = lineWidth or 340
1436 |
1437 | local textTableDisplay = {}
1438 | if type(displayValue) == "function" then
1439 | displayValue = displayValue()
1440 | end
1441 |
1442 | if type(displayValue) == "string" then
1443 | textTableDisplay = { displayValue }
1444 | elseif type(displayValue) == "table" then
1445 | textTableDisplay = SaveHelper.CopyTable(displayValue)
1446 | else
1447 | textTableDisplay = { tostring(displayValue) }
1448 | end
1449 |
1450 | if type(textTableDisplay) == "string" then
1451 | textTableDisplay = { textTableDisplay }
1452 | end
1453 |
1454 | --create new lines based on $newline modifier
1455 | local textTableDisplayAfterNewlines = {}
1456 | for lineIndex = 1, #textTableDisplay do
1457 | local line = textTableDisplay[lineIndex]
1458 | local startIdx, endIdx = string.find(line, "$newline")
1459 | while startIdx do
1460 | local newline = string.sub(line, 0, startIdx - 1)
1461 | table.insert(textTableDisplayAfterNewlines, newline)
1462 |
1463 | line = string.sub(line, endIdx + 1)
1464 |
1465 | startIdx, endIdx = string.find(line, "$newline")
1466 | end
1467 | table.insert(textTableDisplayAfterNewlines, line)
1468 | end
1469 |
1470 | --dynamic string new line creation, based on code by Wofsauge
1471 | local textTableDisplayAfterWordLength = {}
1472 | for lineIndex = 1, #textTableDisplayAfterNewlines do
1473 | local line = textTableDisplayAfterNewlines[lineIndex]
1474 | local curLength = 0
1475 | local text = ""
1476 | for word in string.gmatch(tostring(line), "([^%s]+)") do
1477 | local wordLength = font:GetStringWidthUTF8(word)
1478 |
1479 | if curLength + wordLength <= lineWidth or curLength < 12 then
1480 | text = text .. word .. " "
1481 | curLength = curLength + wordLength
1482 | else
1483 | table.insert(textTableDisplayAfterWordLength, text)
1484 | text = word .. " "
1485 | curLength = wordLength
1486 | end
1487 | end
1488 | table.insert(textTableDisplayAfterWordLength, text)
1489 | end
1490 |
1491 | return textTableDisplayAfterWordLength
1492 | end
1493 |
1494 | --set up screen corner display for hud offset
1495 | local HudOffsetVisualTopLeft = ModConfigMenu.GetMenuAnm2Sprite("Offset", 0)
1496 | local HudOffsetVisualTopRight = ModConfigMenu.GetMenuAnm2Sprite("Offset", 1)
1497 | local HudOffsetVisualBottomRight = ModConfigMenu.GetMenuAnm2Sprite("Offset", 2)
1498 | local HudOffsetVisualBottomLeft = ModConfigMenu.GetMenuAnm2Sprite("Offset", 3)
1499 |
1500 | --render the menu
1501 | local leftCurrentOffset = 0
1502 | local optionsCurrentOffset = 0
1503 | ModConfigMenu.ControlsEnabled = true
1504 | function ModConfigMenu.PostRender()
1505 | local game = Game()
1506 | local isPaused = game:IsPaused() or AwaitingTextInput
1507 |
1508 | local sfx = SFXManager()
1509 |
1510 | local pressingButton = ""
1511 |
1512 | local pressingNonRebindableKey = false
1513 | local pressedToggleMenu = false
1514 |
1515 | local openMenuGlobal = Keyboard.KEY_F10
1516 | local openMenuKeyboard = ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard
1517 | local openMenuController = ModConfigMenu.Config["Mod Config Menu"].OpenMenuController
1518 |
1519 | local takeScreenshot = Keyboard.KEY_F12
1520 |
1521 | --handle version display on game start
1522 | if versionPrintTimer > 0 then
1523 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
1524 |
1525 | local openMenuButton = Keyboard.KEY_F10
1526 | if type(ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard) == "number" and
1527 | ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard > -1 then
1528 | openMenuButton = ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard
1529 | end
1530 |
1531 | local openMenuButtonString = "Unknown Key"
1532 | if InputHelper.KeyboardToString[openMenuButton] then
1533 | openMenuButtonString = InputHelper.KeyboardToString[openMenuButton]
1534 | end
1535 |
1536 | local text = "Press " .. openMenuButtonString .. " to open Mod Config Menu"
1537 | local versionPrintColor = KColor(1, 1, 0, (math.min(versionPrintTimer, 60) / 60) * 0.5)
1538 | versionPrintFont:DrawString(text, 0, bottomRight.Y - 28, versionPrintColor, math.floor(bottomRight.X), true)
1539 | end
1540 |
1541 | --on-screen warnings
1542 | if restartWarnMessage or rerunWarnMessage then
1543 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
1544 |
1545 | local text = restartWarnMessage or rerunWarnMessage
1546 | local warningPrintColor = KColor(1, 0, 0, 1)
1547 | versionPrintFont:DrawString(text, 0, bottomRight.Y - 28, warningPrintColor, math.floor(bottomRight.X), true)
1548 | end
1549 |
1550 | --handle toggling the menu
1551 | if ModConfigMenu.ControlsEnabled and not isPaused then
1552 | for i = 0, 4 do
1553 | if InputHelper.KeyboardTriggered(openMenuGlobal, i)
1554 | or (openMenuKeyboard > -1 and InputHelper.KeyboardTriggered(openMenuKeyboard, i))
1555 | or (openMenuController > -1 and Input.IsButtonTriggered(openMenuController, i)) then
1556 | pressingNonRebindableKey = true
1557 | pressedToggleMenu = true
1558 | if not configMenuInPopup then
1559 | ModConfigMenu.ToggleConfigMenu()
1560 | end
1561 | end
1562 |
1563 | if InputHelper.KeyboardTriggered(takeScreenshot, i) then
1564 | pressingNonRebindableKey = true
1565 | end
1566 | end
1567 | end
1568 |
1569 | --force close the menu in some situations
1570 | if ModConfigMenu.IsVisible then
1571 | if isPaused then
1572 | ModConfigMenu.CloseConfigMenu()
1573 | end
1574 |
1575 | if not ModConfigMenu.RoomIsSafe() then
1576 | ModConfigMenu.CloseConfigMenu()
1577 |
1578 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
1579 | end
1580 | end
1581 |
1582 | --replace dead sea scrolls' controller setting to not conflict with mcm's
1583 | if DeadSeaScrollsMenu and DeadSeaScrollsMenu.GetGamepadToggleSetting then
1584 | local dssControllerToggle = DeadSeaScrollsMenu.GetGamepadToggleSetting()
1585 |
1586 | if DeadSeaScrollsMenu.SaveGamepadToggleSetting then
1587 | if openMenuController == Controller.STICK_RIGHT and
1588 | (dssControllerToggle == 1 or dssControllerToggle == 3 or dssControllerToggle == 4) then
1589 | DeadSeaScrollsMenu.SaveGamepadToggleSetting(2) --force revelations' menu to only use the left stick
1590 | elseif openMenuController == Controller.STICK_LEFT and
1591 | (dssControllerToggle == 1 or dssControllerToggle == 2 or dssControllerToggle == 4) then
1592 | DeadSeaScrollsMenu.SaveGamepadToggleSetting(3) --force revelations' menu to only use the right stick
1593 | end
1594 | end
1595 | end
1596 |
1597 | if ModConfigMenu.IsVisible then
1598 | if ModConfigMenu.ControlsEnabled and not isPaused then
1599 | for i = 0, game:GetNumPlayers() - 1 do
1600 | local player = Isaac.GetPlayer(i)
1601 | local data = player:GetData()
1602 |
1603 | --freeze players and disable their controls
1604 | player.Velocity = vecZero
1605 |
1606 | if not data.ConfigMenuPlayerPosition then
1607 | data.ConfigMenuPlayerPosition = player.Position
1608 | end
1609 | player.Position = data.ConfigMenuPlayerPosition
1610 | if not data.ConfigMenuPlayerControlsDisabled then
1611 | player.ControlsEnabled = false
1612 | data.ConfigMenuPlayerControlsDisabled = true
1613 | end
1614 |
1615 | --disable toggling revelations menu
1616 | if data.input and data.input.menu and data.input.menu.toggle then
1617 | data.input.menu.toggle = false
1618 | end
1619 | end
1620 |
1621 | if not InputHelper.MultipleButtonPressed(ignoreActionButtons) then
1622 | --pressing buttons
1623 | local downButtonPressed = InputHelper.MultipleActionTriggered(actionsDown)
1624 | if downButtonPressed then
1625 | pressingButton = "DOWN"
1626 | end
1627 | local upButtonPressed = InputHelper.MultipleActionTriggered(actionsUp)
1628 | if upButtonPressed then
1629 | pressingButton = "UP"
1630 | end
1631 | local rightButtonPressed = InputHelper.MultipleActionTriggered(actionsRight)
1632 | if rightButtonPressed then
1633 | pressingButton = "RIGHT"
1634 | end
1635 | local leftButtonPressed = InputHelper.MultipleActionTriggered(actionsLeft)
1636 | if leftButtonPressed then
1637 | pressingButton = "LEFT"
1638 | end
1639 | local backButtonPressed = InputHelper.MultipleActionTriggered(actionsBack) or
1640 | InputHelper.MultipleKeyboardTriggered({ Keyboard.KEY_BACKSPACE })
1641 | if backButtonPressed then
1642 | pressingButton = "BACK"
1643 | local possiblyPressedButton = InputHelper.MultipleKeyboardTriggered(Keyboard)
1644 | if possiblyPressedButton then
1645 | ModConfigMenu.Config.LastBackPressed = possiblyPressedButton
1646 | end
1647 | end
1648 | local selectButtonPressed = InputHelper.MultipleActionTriggered(actionsSelect)
1649 | if selectButtonPressed then
1650 | pressingButton = "SELECT"
1651 | local possiblyPressedButton = InputHelper.MultipleKeyboardTriggered(Keyboard)
1652 | if possiblyPressedButton then
1653 | ModConfigMenu.Config.LastSelectPressed = possiblyPressedButton
1654 | end
1655 | end
1656 | if ModConfigMenu.Config["Mod Config Menu"].ResetToDefault > -1 and
1657 | InputHelper.MultipleKeyboardTriggered({ ModConfigMenu.Config["Mod Config Menu"].ResetToDefault }) then
1658 | pressingButton = "RESET"
1659 | end
1660 |
1661 | --holding buttons
1662 | if InputHelper.MultipleActionPressed(actionsDown) then
1663 | holdingCounterDown = holdingCounterDown + 1
1664 | else
1665 | holdingCounterDown = 0
1666 | end
1667 | if holdingCounterDown > 20 and holdingCounterDown % 5 == 0 then
1668 | pressingButton = "DOWN"
1669 | end
1670 | if InputHelper.MultipleActionPressed(actionsUp) then
1671 | holdingCounterUp = holdingCounterUp + 1
1672 | else
1673 | holdingCounterUp = 0
1674 | end
1675 | if holdingCounterUp > 20 and holdingCounterUp % 5 == 0 then
1676 | pressingButton = "UP"
1677 | end
1678 | if InputHelper.MultipleActionPressed(actionsRight) then
1679 | holdingCounterRight = holdingCounterRight + 1
1680 | else
1681 | holdingCounterRight = 0
1682 | end
1683 | if holdingCounterRight > 20 and holdingCounterRight % 5 == 0 then
1684 | pressingButton = "RIGHT"
1685 | end
1686 | if InputHelper.MultipleActionPressed(actionsLeft) then
1687 | holdingCounterLeft = holdingCounterLeft + 1
1688 | else
1689 | holdingCounterLeft = 0
1690 | end
1691 | if holdingCounterLeft > 20 and holdingCounterLeft % 5 == 0 then
1692 | pressingButton = "LEFT"
1693 | end
1694 | else
1695 | if InputHelper.MultipleButtonTriggered({ Controller.BUTTON_B }) then
1696 | pressingButton = "BACK"
1697 | pressingNonRebindableKey = true
1698 | end
1699 | if InputHelper.MultipleButtonTriggered({ Controller.BUTTON_A }) then
1700 | pressingButton = "SELECT"
1701 | pressingNonRebindableKey = true
1702 | end
1703 | end
1704 |
1705 | if pressingButton ~= "" then
1706 | pressingNonRebindableKey = true
1707 | end
1708 | end
1709 |
1710 | updateCurrentMenuVars()
1711 |
1712 | local lastCursorCategoryPosition = configMenuPositionCursorCategory
1713 | local lastCursorSubcategoryPosition = configMenuPositionCursorSubcategory
1714 | local lastCursorOptionsPosition = configMenuPositionCursorOption
1715 |
1716 | local enterPopup = false
1717 | local leavePopup = false
1718 |
1719 | local optionChanged = false
1720 |
1721 | local enterOptions = false
1722 | local leaveOptions = false
1723 |
1724 | local enterSubcategory = false
1725 | local leaveSubcategory = false
1726 |
1727 | if configMenuInPopup then
1728 | if currentMenuOption then
1729 | local optionType = currentMenuOption.Type
1730 | local optionCurrent = currentMenuOption.CurrentSetting
1731 | local optionOnChange = currentMenuOption.OnChange
1732 |
1733 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD
1734 | or optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER
1735 | or currentMenuOption.OnSelect then
1736 | if not isPaused then
1737 | if pressingNonRebindableKey
1738 | and not (pressingButton == "BACK"
1739 | or pressingButton == "LEFT"
1740 | or (currentMenuOption.OnSelect and (pressingButton == "SELECT" or pressingButton == "RIGHT"))
1741 | or (currentMenuOption.IsResetKeybind and pressingButton == "RESET")
1742 | or (currentMenuOption.IsOpenMenuKeybind and pressedToggleMenu)) then
1743 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
1744 | else
1745 | local numberToChange = nil
1746 | local receivedInput = false
1747 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
1748 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
1749 | numberToChange = optionCurrent
1750 |
1751 | if type(optionCurrent) == "function" then
1752 | numberToChange = optionCurrent()
1753 | end
1754 |
1755 | if pressingButton == "BACK" or pressingButton == "LEFT" then
1756 | numberToChange = nil
1757 | receivedInput = true
1758 | else
1759 | for i = 0, 4 do
1760 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
1761 | for j = 32, 400 do
1762 | if InputHelper.KeyboardTriggered(j, i) then
1763 | numberToChange = j
1764 | receivedInput = true
1765 | break
1766 | end
1767 | end
1768 | else
1769 | for j = 0, 31 do
1770 | if Input.IsButtonTriggered(j, i) then
1771 | numberToChange = j
1772 | receivedInput = true
1773 | break
1774 | end
1775 | end
1776 | end
1777 | end
1778 | end
1779 | elseif currentMenuOption.OnSelect then
1780 | if pressingButton == "BACK" or pressingButton == "LEFT" then
1781 | receivedInput = true
1782 | end
1783 | if pressingButton == "SELECT" or pressingButton == "RIGHT" then
1784 | numberToChange = true
1785 | receivedInput = true
1786 | end
1787 | end
1788 |
1789 | if receivedInput then
1790 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
1791 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
1792 | if type(optionCurrent) == "function" then
1793 | if optionOnChange then
1794 | optionOnChange(numberToChange)
1795 | end
1796 | elseif type(optionCurrent) == "number" then
1797 | currentMenuOption.CurrentSetting = numberToChange
1798 | end
1799 |
1800 | --callback
1801 | --[[
1802 | CustomCallbackHelper.CallCallbacks
1803 | (
1804 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
1805 | nil,
1806 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
1807 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
1808 | )
1809 | --]]
1810 | elseif currentMenuOption.OnSelect and numberToChange then
1811 | currentMenuOption.OnSelect()
1812 | end
1813 |
1814 | leavePopup = true
1815 |
1816 | local sound = currentMenuOption.Sound
1817 | if not sound then
1818 | sound = SoundEffect.SOUND_PLOP
1819 | end
1820 | if sound >= 0 then
1821 | sfx:Play(sound, 1, 0, false, 1)
1822 | end
1823 | end
1824 | end
1825 | end
1826 | end
1827 | end
1828 |
1829 | if currentMenuOption.Restart or currentMenuOption.Rerun then
1830 | --confirmed left press
1831 | if pressingButton == "RIGHT" then
1832 | leavePopup = true
1833 | end
1834 |
1835 | --confirmed back press
1836 | if pressingButton == "SELECT" then
1837 | leavePopup = true
1838 | end
1839 | end
1840 |
1841 | --confirmed left press
1842 | if pressingButton == "LEFT" then
1843 | leavePopup = true
1844 | end
1845 |
1846 | --confirmed back press
1847 | if pressingButton == "BACK" then
1848 | leavePopup = true
1849 | end
1850 | elseif configMenuInOptions then
1851 | --confirmed down press
1852 | if pressingButton == "DOWN" then
1853 | configMenuPositionCursorOption = configMenuPositionCursorOption + 1 --move options cursor down
1854 | end
1855 |
1856 | --confirmed up press
1857 | if pressingButton == "UP" then
1858 | configMenuPositionCursorOption = configMenuPositionCursorOption - 1 --move options cursor up
1859 | end
1860 |
1861 | if pressingButton == "SELECT" or pressingButton == "RIGHT" or pressingButton == "LEFT" or
1862 | (pressingButton == "RESET" and currentMenuOption and currentMenuOption.Default ~= nil) then
1863 | if pressingButton == "LEFT" then
1864 | leaveOptions = true
1865 | end
1866 |
1867 | if currentMenuOption then
1868 | local optionType = currentMenuOption.Type
1869 | local optionCurrent = currentMenuOption.CurrentSetting
1870 | local optionOnChange = currentMenuOption.OnChange
1871 |
1872 | if optionType == ModConfigMenu.OptionType.SCROLL or optionType == ModConfigMenu.OptionType.NUMBER then
1873 | leaveOptions = false
1874 |
1875 | local numberToChange = optionCurrent
1876 |
1877 | if type(optionCurrent) == "function" then
1878 | numberToChange = optionCurrent()
1879 | end
1880 |
1881 | local modifyBy = currentMenuOption.ModifyBy or 1
1882 | modifyBy = math.max(modifyBy, 0.001)
1883 | if math.floor(modifyBy) == modifyBy then --force modify by into being an integer instead of a float if it should be
1884 | modifyBy = math.floor(modifyBy)
1885 | end
1886 |
1887 | if pressingButton == "RIGHT" or pressingButton == "SELECT" then
1888 | numberToChange = numberToChange + modifyBy
1889 | elseif pressingButton == "LEFT" then
1890 | numberToChange = numberToChange - modifyBy
1891 | elseif pressingButton == "RESET" and currentMenuOption.Default ~= nil then
1892 | numberToChange = currentMenuOption.Default
1893 | if type(currentMenuOption.Default) == "function" then
1894 | numberToChange = currentMenuOption.Default()
1895 | end
1896 | end
1897 |
1898 | if optionType == ModConfigMenu.OptionType.SCROLL then
1899 | numberToChange = math.max(math.min(math.floor(numberToChange), 10), 0)
1900 | else
1901 | if currentMenuOption.Maximum and numberToChange > currentMenuOption.Maximum then
1902 | if not currentMenuOption.NoLoopFromMaxMin and currentMenuOption.Minimum then
1903 | numberToChange = currentMenuOption.Minimum
1904 | else
1905 | numberToChange = currentMenuOption.Maximum
1906 | end
1907 | end
1908 | if currentMenuOption.Minimum and numberToChange < currentMenuOption.Minimum then
1909 | if not currentMenuOption.NoLoopFromMaxMin and currentMenuOption.Maximum then
1910 | numberToChange = currentMenuOption.Maximum
1911 | else
1912 | numberToChange = currentMenuOption.Minimum
1913 | end
1914 | end
1915 | end
1916 |
1917 | if math.floor(modifyBy) ~= modifyBy then --check if modify by is a float
1918 | numberToChange = math.floor((numberToChange * 1000) + 0.5) * 0.001
1919 | else
1920 | numberToChange = math.floor(numberToChange)
1921 | end
1922 |
1923 | if type(optionCurrent) == "function" then
1924 | if optionOnChange then
1925 | optionOnChange(numberToChange)
1926 | end
1927 | optionChanged = true
1928 | elseif type(optionCurrent) == "number" then
1929 | currentMenuOption.CurrentSetting = numberToChange
1930 | optionChanged = true
1931 | end
1932 |
1933 | --callback
1934 | --[[
1935 | CustomCallbackHelper.CallCallbacks
1936 | (
1937 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
1938 | nil,
1939 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
1940 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
1941 | )
1942 | --]]
1943 | local sound = currentMenuOption.Sound
1944 | if not sound then
1945 | sound = SoundEffect.SOUND_PLOP
1946 | end
1947 | if sound >= 0 then
1948 | sfx:Play(sound, 1, 0, false, 1)
1949 | end
1950 | elseif optionType == ModConfigMenu.OptionType.BOOLEAN then
1951 | leaveOptions = false
1952 |
1953 | local boolToChange = optionCurrent
1954 |
1955 | if type(optionCurrent) == "function" then
1956 | boolToChange = optionCurrent()
1957 | end
1958 |
1959 | if pressingButton == "RESET" and currentMenuOption.Default ~= nil then
1960 | boolToChange = currentMenuOption.Default
1961 | if type(currentMenuOption.Default) == "function" then
1962 | boolToChange = currentMenuOption.Default()
1963 | end
1964 | else
1965 | boolToChange = (not boolToChange)
1966 | end
1967 |
1968 | if type(optionCurrent) == "function" then
1969 | if optionOnChange then
1970 | optionOnChange(boolToChange)
1971 | end
1972 | optionChanged = true
1973 | elseif type(optionCurrent) == "boolean" then
1974 | currentMenuOption.CurrentSetting = boolToChange
1975 | optionChanged = true
1976 | end
1977 |
1978 | --callback
1979 | --[[
1980 | CustomCallbackHelper.CallCallbacks
1981 | (
1982 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
1983 | nil,
1984 | {currentMenuOption.CurrentSetting, boolToChange}, --args to send
1985 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
1986 | )
1987 | --]]
1988 | local sound = currentMenuOption.Sound
1989 | if not sound then
1990 | sound = SoundEffect.SOUND_PLOP
1991 | end
1992 | if sound >= 0 then
1993 | sfx:Play(sound, 1, 0, false, 1)
1994 | end
1995 | elseif (
1996 | optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
1997 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER) and pressingButton == "RESET" and
1998 | currentMenuOption.Default ~= nil then
1999 | local numberToChange = optionCurrent
2000 |
2001 | if type(optionCurrent) == "function" then
2002 | numberToChange = optionCurrent()
2003 | end
2004 |
2005 | numberToChange = currentMenuOption.Default
2006 | if type(currentMenuOption.Default) == "function" then
2007 | numberToChange = currentMenuOption.Default()
2008 | end
2009 |
2010 | if type(optionCurrent) == "function" then
2011 | if optionOnChange then
2012 | optionOnChange(numberToChange)
2013 | end
2014 | optionChanged = true
2015 | elseif type(optionCurrent) == "number" then
2016 | currentMenuOption.CurrentSetting = numberToChange
2017 | optionChanged = true
2018 | end
2019 |
2020 | --callback
2021 | --[[
2022 | CustomCallbackHelper.CallCallbacks
2023 | (
2024 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
2025 | nil,
2026 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
2027 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
2028 | )
2029 | --]]
2030 | local sound = currentMenuOption.Sound
2031 | if not sound then
2032 | sound = SoundEffect.SOUND_PLOP
2033 | end
2034 | if sound >= 0 then
2035 | sfx:Play(sound, 1, 0, false, 1)
2036 | end
2037 | elseif optionType ~= ModConfigMenu.OptionType.SPACE and pressingButton == "RIGHT" then
2038 | if currentMenuOption.Popup then
2039 | enterPopup = true
2040 | elseif currentMenuOption.OnSelect then
2041 | currentMenuOption.OnSelect()
2042 | end
2043 | end
2044 | end
2045 | end
2046 |
2047 | --confirmed back press
2048 | if pressingButton == "BACK" then
2049 | leaveOptions = true
2050 | end
2051 |
2052 | --confirmed select press
2053 | if pressingButton == "SELECT" then
2054 | if currentMenuOption then
2055 | if currentMenuOption.Popup then
2056 | enterPopup = true
2057 | elseif currentMenuOption.OnSelect then
2058 | currentMenuOption.OnSelect()
2059 | end
2060 | end
2061 | end
2062 |
2063 | --reset command
2064 | if optionChanged then
2065 | if currentMenuOption.Restart or currentMenuOption.Rerun then
2066 | enterPopup = true
2067 | end
2068 | end
2069 | elseif configMenuInSubcategory then
2070 | local hasUsableCategories = false
2071 | if currentMenuCategory.Subcategories then
2072 | for j = 1, #currentMenuCategory.Subcategories do
2073 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2074 | hasUsableCategories = true
2075 | end
2076 | end
2077 | end
2078 | if hasUsableCategories then
2079 | --confirmed down press
2080 | if pressingButton == "DOWN" then
2081 | enterOptions = true
2082 | end
2083 |
2084 | --confirmed up press
2085 | if pressingButton == "UP" then
2086 | leaveSubcategory = true
2087 | end
2088 |
2089 | --confirmed right press
2090 | if pressingButton == "RIGHT" then
2091 | configMenuPositionCursorSubcategory = configMenuPositionCursorSubcategory + 1 --move right down
2092 | end
2093 |
2094 | --confirmed left press
2095 | if pressingButton == "LEFT" then
2096 | configMenuPositionCursorSubcategory = configMenuPositionCursorSubcategory - 1 --move cursor left
2097 | end
2098 |
2099 | --confirmed back press
2100 | if pressingButton == "BACK" then
2101 | leaveSubcategory = true
2102 | end
2103 |
2104 | --confirmed select press
2105 | if pressingButton == "SELECT" then
2106 | enterOptions = true
2107 | end
2108 | end
2109 | else
2110 | --confirmed down press
2111 | if pressingButton == "DOWN" then
2112 | configMenuPositionCursorCategory = configMenuPositionCursorCategory + 1 --move left cursor down
2113 | end
2114 |
2115 | --confirmed up press
2116 | if pressingButton == "UP" then
2117 | configMenuPositionCursorCategory = configMenuPositionCursorCategory - 1 --move left cursor up
2118 | end
2119 |
2120 | --confirmed right press
2121 | if pressingButton == "RIGHT" then
2122 | enterSubcategory = true
2123 | end
2124 |
2125 | --confirmed back press
2126 | if pressingButton == "BACK" then
2127 | ModConfigMenu.CloseConfigMenu()
2128 | end
2129 |
2130 | --confirmed select press
2131 | if pressingButton == "SELECT" then
2132 | enterSubcategory = true
2133 | end
2134 | end
2135 |
2136 | --entering popup
2137 | if enterPopup then
2138 | ModConfigMenu.EnterPopup()
2139 | end
2140 |
2141 | --leaving popup
2142 | if leavePopup then
2143 | ModConfigMenu.LeavePopup()
2144 | end
2145 |
2146 | --entering subcategory
2147 | if enterSubcategory then
2148 | ModConfigMenu.EnterSubcategory()
2149 | end
2150 |
2151 | --entering options
2152 | if enterOptions then
2153 | ModConfigMenu.EnterOptions()
2154 | end
2155 |
2156 | --leaving options
2157 | if leaveOptions then
2158 | ModConfigMenu.LeaveOptions()
2159 | end
2160 |
2161 | --leaving subcategory
2162 | if leaveSubcategory then
2163 | ModConfigMenu.LeaveSubcategory()
2164 | end
2165 |
2166 | --category cursor position was changed
2167 | if lastCursorCategoryPosition ~= configMenuPositionCursorCategory then
2168 | if not configMenuInSubcategory then
2169 | --cursor position
2170 | if configMenuPositionCursorCategory < 1 then --move from the top of the list to the bottom
2171 | configMenuPositionCursorCategory = #ModConfigMenu.MenuData
2172 | end
2173 | if configMenuPositionCursorCategory > #ModConfigMenu.MenuData then --move from the bottom of the list to the top
2174 | configMenuPositionCursorCategory = 1
2175 | end
2176 |
2177 | --make sure subcategory and option positions are 1
2178 | configMenuPositionCursorSubcategory = 1
2179 | configMenuPositionFirstSubcategory = 1
2180 | configMenuPositionCursorOption = 1
2181 | optionsCurrentOffset = 0
2182 | end
2183 | end
2184 |
2185 | --subcategory cursor position was changed
2186 | if lastCursorSubcategoryPosition ~= configMenuPositionCursorSubcategory then
2187 | if not configMenuInOptions then
2188 | --cursor position
2189 | if configMenuPositionCursorSubcategory < 1 then --move from the top of the list to the bottom
2190 | configMenuPositionCursorSubcategory = #currentMenuCategory.Subcategories
2191 | end
2192 | if configMenuPositionCursorSubcategory > #currentMenuCategory.Subcategories then --move from the bottom of the list to the top
2193 | configMenuPositionCursorSubcategory = 1
2194 | end
2195 |
2196 | --first category selection to render
2197 | if configMenuPositionFirstSubcategory > 1 and
2198 | configMenuPositionCursorSubcategory <= configMenuPositionFirstSubcategory + 1 then
2199 | configMenuPositionFirstSubcategory = configMenuPositionCursorSubcategory - 1
2200 | end
2201 | if configMenuPositionFirstSubcategory + (configMenuSubcategoriesCanShow - 1) < #
2202 | currentMenuCategory.Subcategories and
2203 | configMenuPositionCursorSubcategory >= 1 + (configMenuSubcategoriesCanShow - 2) then
2204 | configMenuPositionFirstSubcategory = configMenuPositionCursorSubcategory -
2205 | (configMenuSubcategoriesCanShow - 2
2206 | )
2207 | end
2208 | configMenuPositionFirstSubcategory = math.min(math.max(configMenuPositionFirstSubcategory, 1),
2209 | #currentMenuCategory.Subcategories - (configMenuSubcategoriesCanShow - 1))
2210 |
2211 | --make sure option positions are 1
2212 | configMenuPositionCursorOption = 1
2213 | optionsCurrentOffset = 0
2214 | end
2215 | end
2216 |
2217 | --options cursor position was changed
2218 | if lastCursorOptionsPosition ~= configMenuPositionCursorOption then
2219 | if configMenuInOptions
2220 | and currentMenuSubcategory
2221 | and currentMenuSubcategory.Options
2222 | and #currentMenuSubcategory.Options > 0 then
2223 | --find next valid option that isn't a space
2224 | local nextValidOptionSelection = configMenuPositionCursorOption
2225 | local optionIndex = configMenuPositionCursorOption
2226 | for i = 1, #currentMenuSubcategory.Options * 2 do
2227 | local thisOption = currentMenuSubcategory.Options[optionIndex]
2228 |
2229 | if thisOption
2230 | and thisOption.Type
2231 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
2232 | and
2233 | (
2234 | not thisOption.NoCursorHere or
2235 | (type(thisOption.NoCursorHere) == "function" and not thisOption.NoCursorHere()))
2236 | and thisOption.Display then
2237 | nextValidOptionSelection = optionIndex
2238 |
2239 | break
2240 | end
2241 |
2242 | if configMenuPositionCursorOption > lastCursorOptionsPosition then
2243 | optionIndex = optionIndex + 1
2244 | elseif configMenuPositionCursorOption < lastCursorOptionsPosition then
2245 | optionIndex = optionIndex - 1
2246 | end
2247 | if optionIndex < 1 then
2248 | optionIndex = #currentMenuSubcategory.Options
2249 | end
2250 | if optionIndex > #currentMenuSubcategory.Options then
2251 | optionIndex = 1
2252 | end
2253 | end
2254 |
2255 | configMenuPositionCursorOption = nextValidOptionSelection
2256 |
2257 | updateCurrentMenuVars()
2258 |
2259 | --first options selection to render
2260 | local hasSubcategories = false
2261 | for j = 1, #currentMenuCategory.Subcategories do
2262 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2263 | hasSubcategories = true
2264 | end
2265 | end
2266 | if hasSubcategories then
2267 | --todo
2268 | end
2269 | end
2270 | end
2271 |
2272 | local centerPos = ScreenHelper.GetScreenCenter()
2273 |
2274 | --title pos handling
2275 | local titlePos = centerPos + Vector(68, -118)
2276 |
2277 | --left pos handling
2278 |
2279 | local leftDesiredOffset = 0
2280 | local leftCanScrollUp = false
2281 | local leftCanScrollDown = false
2282 |
2283 | local numLeft = #ModConfigMenu.MenuData
2284 |
2285 | local leftPos = centerPos + Vector( -142, -102)
2286 | local leftPosTopmost = centerPos.Y - 116
2287 | local leftPosBottommost = centerPos.Y + 90
2288 |
2289 | if numLeft > 7 then
2290 | if configMenuPositionCursorCategory > 6 then
2291 | leftCanScrollUp = true
2292 |
2293 | local cursorScroll = configMenuPositionCursorCategory - 6
2294 | local maxLeftScroll = numLeft - 8
2295 | leftDesiredOffset = math.min(cursorScroll, maxLeftScroll) * -14
2296 |
2297 | if cursorScroll < maxLeftScroll then
2298 | leftCanScrollDown = true
2299 | end
2300 | else
2301 | leftCanScrollDown = true
2302 | end
2303 | end
2304 |
2305 | if leftDesiredOffset ~= leftCurrentOffset then
2306 | local modifyOffset = math.floor(leftDesiredOffset - leftCurrentOffset) / 10
2307 | if modifyOffset > -0.1 and modifyOffset < 0 then
2308 | modifyOffset = -0.1
2309 | end
2310 | if modifyOffset < 0.1 and modifyOffset > 0 then
2311 | modifyOffset = 0.1
2312 | end
2313 |
2314 | leftCurrentOffset = leftCurrentOffset + modifyOffset
2315 | if (leftDesiredOffset - leftCurrentOffset) < 0.25 and (leftDesiredOffset - leftCurrentOffset) > -0.25 then
2316 | leftCurrentOffset = leftDesiredOffset
2317 | end
2318 | end
2319 |
2320 | if leftCurrentOffset ~= 0 then
2321 | leftPos = leftPos + Vector(0, leftCurrentOffset)
2322 | end
2323 |
2324 | --options pos handling
2325 | local optionsDesiredOffset = 0
2326 | local optionsCanScrollUp = false
2327 | local optionsCanScrollDown = false
2328 |
2329 | local numOptions = 0
2330 |
2331 | local optionPos = centerPos + Vector(68, -18)
2332 | local optionPosTopmost = centerPos.Y - 108
2333 | local optionPosBottommost = centerPos.Y + 86
2334 |
2335 | if currentMenuSubcategory
2336 | and currentMenuSubcategory.Options
2337 | and #currentMenuSubcategory.Options > 0 then
2338 | numOptions = #currentMenuSubcategory.Options
2339 |
2340 | local hasSubcategories = false
2341 | if currentMenuCategory.Subcategories then
2342 | for j = 1, #currentMenuCategory.Subcategories do
2343 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2344 | numOptions = numOptions + 2
2345 | hasSubcategories = true
2346 | break
2347 | end
2348 | end
2349 | end
2350 |
2351 | if hasSubcategories then
2352 | optionPos = optionPos + Vector(0, -70)
2353 | else
2354 | optionPos = optionPos + Vector(0, math.min(numOptions - 1, 10) * -7)
2355 | end
2356 |
2357 | if numOptions > 12 then
2358 | if configMenuPositionCursorOption > 6 and configMenuInOptions then
2359 | optionsCanScrollUp = true
2360 |
2361 | local cursorScroll = configMenuPositionCursorOption - 6
2362 | local maxOptionsScroll = numOptions - 12
2363 | optionsDesiredOffset = math.min(cursorScroll, maxOptionsScroll) * -14
2364 |
2365 | if cursorScroll < maxOptionsScroll then
2366 | optionsCanScrollDown = true
2367 | end
2368 | else
2369 | optionsCanScrollDown = true
2370 | end
2371 | end
2372 | end
2373 |
2374 | if optionsDesiredOffset ~= optionsCurrentOffset then
2375 | local modifyOffset = math.floor(optionsDesiredOffset - optionsCurrentOffset) / 10
2376 | if modifyOffset > -0.1 and modifyOffset < 0 then
2377 | modifyOffset = -0.1
2378 | end
2379 | if modifyOffset < 0.1 and modifyOffset > 0 then
2380 | modifyOffset = 0.1
2381 | end
2382 |
2383 | optionsCurrentOffset = optionsCurrentOffset + modifyOffset
2384 | if (optionsDesiredOffset - optionsCurrentOffset) < 0.25 and (optionsDesiredOffset - optionsCurrentOffset) > -0.25 then
2385 | optionsCurrentOffset = optionsDesiredOffset
2386 | end
2387 | end
2388 |
2389 | if optionsCurrentOffset ~= 0 then
2390 | optionPos = optionPos + Vector(0, optionsCurrentOffset)
2391 | end
2392 |
2393 | --info pos handling
2394 | local infoPos = centerPos + Vector( -4, 106)
2395 |
2396 | MenuSprite:Render(centerPos, vecZero, vecZero)
2397 |
2398 | --get if controls can be shown
2399 | local shouldShowControls = true
2400 | if configMenuInOptions and currentMenuOption and currentMenuOption.HideControls then
2401 | shouldShowControls = false
2402 | end
2403 | if not ModConfigMenu.Config["Mod Config Menu"].ShowControls then
2404 | shouldShowControls = false
2405 | end
2406 |
2407 | --category
2408 | local lastLeftPos = leftPos
2409 | local renderedLeft = 0
2410 | for categoryIndex = 1, #ModConfigMenu.MenuData do
2411 | --text
2412 | if lastLeftPos.Y > leftPosTopmost and lastLeftPos.Y < leftPosBottommost then
2413 | local textToDraw = tostring(ModConfigMenu.MenuData[categoryIndex].Name)
2414 |
2415 | local color = leftFontColor
2416 | --[[
2417 | if configMenuPositionCursorCategory == categoryIndex then
2418 | color = leftFontColorSelected
2419 | end
2420 | ]]
2421 | local posOffset = Font12:GetStringWidthUTF8(textToDraw) / 2
2422 | Font12:DrawString(textToDraw, lastLeftPos.X - posOffset, lastLeftPos.Y - 8, color, 0, true)
2423 |
2424 | --cursor
2425 | if configMenuPositionCursorCategory == categoryIndex then
2426 | CursorSpriteRight:Render(lastLeftPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2427 | end
2428 | end
2429 |
2430 | --increase counter
2431 | renderedLeft = renderedLeft + 1
2432 |
2433 | --pos mod
2434 | lastLeftPos = lastLeftPos + Vector(0, 16)
2435 | end
2436 |
2437 | --render scroll arrows
2438 | if leftCanScrollUp then
2439 | CursorSpriteUp:Render(centerPos + Vector( -78, -104), vecZero, vecZero) --up arrow
2440 | end
2441 | if leftCanScrollDown then
2442 | CursorSpriteDown:Render(centerPos + Vector( -78, 70), vecZero, vecZero) --down arrow
2443 | end
2444 |
2445 | ------------------------
2446 | --RENDER SUBCATEGORIES--
2447 | ------------------------
2448 |
2449 | local lastOptionPos = optionPos
2450 | local renderedOptions = 0
2451 |
2452 | if currentMenuCategory then
2453 | local hasUncategorizedCategory = false
2454 | local hasSubcategories = false
2455 | local numCategories = 0
2456 | for j = 1, #currentMenuCategory.Subcategories do
2457 | if currentMenuCategory.Subcategories[j].Name == "Uncategorized" then
2458 | hasUncategorizedCategory = true
2459 | else
2460 | hasSubcategories = true
2461 | numCategories = numCategories + 1
2462 | end
2463 | end
2464 |
2465 | if hasSubcategories then
2466 | if hasUncategorizedCategory then
2467 | numCategories = numCategories + 1
2468 | end
2469 |
2470 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2471 | local lastSubcategoryPos = optionPos
2472 | if numCategories == 2 then
2473 | lastSubcategoryPos = lastOptionPos + Vector( -38, 0)
2474 | elseif numCategories >= 3 then
2475 | lastSubcategoryPos = lastOptionPos + Vector( -76, 0)
2476 | end
2477 |
2478 | local renderedSubcategories = 0
2479 |
2480 | for subcategoryIndex = 1, #currentMenuCategory.Subcategories do
2481 | if subcategoryIndex >= configMenuPositionFirstSubcategory then
2482 | local thisSubcategory = currentMenuCategory.Subcategories[subcategoryIndex]
2483 |
2484 | local posOffset = 0
2485 |
2486 | if thisSubcategory.Name then
2487 | local textToDraw = thisSubcategory.Name
2488 |
2489 | textToDraw = tostring(textToDraw)
2490 |
2491 | local color = subcategoryFontColor
2492 | if not configMenuInSubcategory then
2493 | color = subcategoryFontColorAlpha
2494 | --[[
2495 | elseif configMenuPositionCursorSubcategory == subcategoryIndex and configMenuInSubcategory then
2496 | color = subcategoryFontColorSelected
2497 | ]]
2498 | end
2499 |
2500 | posOffset = Font12:GetStringWidthUTF8(textToDraw) / 2
2501 | Font12:DrawString(textToDraw, lastSubcategoryPos.X - posOffset, lastSubcategoryPos.Y - 8, color, 0, true)
2502 | end
2503 |
2504 | --cursor
2505 | if configMenuPositionCursorSubcategory == subcategoryIndex and configMenuInSubcategory then
2506 | CursorSpriteRight:Render(lastSubcategoryPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2507 | end
2508 |
2509 | --increase counter
2510 | renderedSubcategories = renderedSubcategories + 1
2511 |
2512 | if renderedSubcategories >= configMenuSubcategoriesCanShow then --if this is the last one we should render
2513 | --render scroll arrows
2514 | if configMenuPositionFirstSubcategory > 1 then --if the first one we rendered wasn't the first in the list
2515 | SubcategoryCursorSpriteLeft:Render(lastOptionPos + Vector( -125, 0), vecZero, vecZero)
2516 | end
2517 |
2518 | if subcategoryIndex < #currentMenuCategory.Subcategories then --if this is not the last thing
2519 | SubcategoryCursorSpriteRight:Render(lastOptionPos + Vector(125, 0), vecZero, vecZero)
2520 | end
2521 |
2522 | break
2523 | end
2524 |
2525 | --pos mod
2526 | lastSubcategoryPos = lastSubcategoryPos + Vector(76, 0)
2527 | end
2528 | end
2529 | end
2530 |
2531 | --subcategory selection counts as an option that gets rendered
2532 | renderedOptions = renderedOptions + 1
2533 | lastOptionPos = lastOptionPos + Vector(0, 14)
2534 |
2535 | --subcategory to options divider
2536 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2537 | SubcategoryDividerSprite:Render(lastOptionPos, vecZero, vecZero)
2538 | end
2539 |
2540 | --subcategory to options divider counts as an option that gets rendered
2541 | renderedOptions = renderedOptions + 1
2542 | lastOptionPos = lastOptionPos + Vector(0, 14)
2543 | end
2544 | end
2545 |
2546 | ------------------
2547 | --RENDER OPTIONS--
2548 | ------------------
2549 |
2550 | local firstOptionPos = lastOptionPos
2551 |
2552 | if currentMenuSubcategory
2553 | and currentMenuSubcategory.Options
2554 | and #currentMenuSubcategory.Options > 0 then
2555 | for optionIndex = 1, #currentMenuSubcategory.Options do
2556 | local thisOption = currentMenuSubcategory.Options[optionIndex]
2557 |
2558 | local cursorIsAtThisOption = configMenuPositionCursorOption == optionIndex and configMenuInOptions
2559 | local posOffset = 10
2560 |
2561 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2562 | if thisOption.Type
2563 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
2564 | and thisOption.Display then
2565 | local optionType = thisOption.Type
2566 | local optionDisplay = thisOption.Display
2567 | local optionColor = thisOption.Color
2568 |
2569 | local useAltSlider = thisOption.AltSlider
2570 |
2571 | --get what to draw
2572 | if optionType == ModConfigMenu.OptionType.TEXT
2573 | or optionType == ModConfigMenu.OptionType.BOOLEAN
2574 | or optionType == ModConfigMenu.OptionType.NUMBER
2575 | or optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD
2576 | or optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER
2577 | or optionType == ModConfigMenu.OptionType.TITLE then
2578 | local textToDraw = optionDisplay
2579 |
2580 | if type(optionDisplay) == "function" then
2581 | textToDraw = optionDisplay(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
2582 | end
2583 |
2584 | textToDraw = tostring(textToDraw)
2585 |
2586 | local heightOffset = 6
2587 | local font = Font10
2588 | local color = optionsFontColor
2589 | if not configMenuInOptions then
2590 | if thisOption.NoCursorHere then
2591 | color = optionsFontColorNoCursorAlpha
2592 | else
2593 | color = optionsFontColorAlpha
2594 | end
2595 | elseif thisOption.NoCursorHere then
2596 | color = optionsFontColorNoCursor
2597 | end
2598 | if optionType == ModConfigMenu.OptionType.TITLE then
2599 | heightOffset = 8
2600 | font = Font12
2601 | color = optionsFontColorTitle
2602 | if not configMenuInOptions then
2603 | color = optionsFontColorTitleAlpha
2604 | end
2605 | end
2606 |
2607 | if optionColor then
2608 | color = KColor(optionColor[1], optionColor[2], optionColor[3], color.Alpha)
2609 | end
2610 |
2611 | posOffset = font:GetStringWidthUTF8(textToDraw) / 2
2612 | font:DrawString(textToDraw, lastOptionPos.X - posOffset, lastOptionPos.Y - heightOffset, color, 0, true)
2613 | elseif optionType == ModConfigMenu.OptionType.SCROLL then
2614 | local numberToShow = optionDisplay
2615 |
2616 | if type(optionDisplay) == "function" then
2617 | numberToShow = optionDisplay(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
2618 | end
2619 |
2620 | posOffset = 31
2621 | local scrollOffset = 0
2622 |
2623 | if type(numberToShow) == "number" then
2624 | numberToShow = math.max(math.min(math.floor(numberToShow), 10), 0)
2625 | elseif type(numberToShow) == "string" then
2626 | local numberToShowStart, numberToShowEnd = string.find(numberToShow, "$scroll")
2627 | if numberToShowStart and numberToShowEnd then
2628 | local numberStart = numberToShowEnd + 1
2629 | local numberEnd = numberToShowEnd + 3
2630 | local numberString = string.sub(numberToShow, numberStart, numberEnd)
2631 | numberString = tonumber(numberString)
2632 | if not numberString or (numberString and not type(numberString) == "number") or
2633 | (numberString and type(numberString) == "number" and numberString < 10) then
2634 | numberEnd = numberEnd - 1
2635 | numberString = string.sub(numberToShow, numberStart, numberEnd)
2636 | numberString = tonumber(numberString)
2637 | end
2638 | if numberString and type(numberString) == "number" then
2639 | local textToDrawPreScroll = string.sub(numberToShow, 0, numberToShowStart - 1)
2640 | local textToDrawPostScroll = string.sub(numberToShow, numberEnd, string.len(numberToShow))
2641 | local textToDraw = textToDrawPreScroll .. " " .. textToDrawPostScroll
2642 |
2643 | local color = optionsFontColor
2644 | if not configMenuInOptions then
2645 | color = optionsFontColorAlpha
2646 | end
2647 | if optionColor then
2648 | color = KColor(optionColor[1], optionColor[2], optionColor[3], color.Alpha)
2649 | end
2650 |
2651 | scrollOffset = posOffset
2652 | posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2653 | Font10:DrawString(textToDraw, lastOptionPos.X - posOffset, lastOptionPos.Y - 6, color, 0, true)
2654 |
2655 | scrollOffset = posOffset - (Font10:GetStringWidthUTF8(textToDrawPreScroll) + scrollOffset)
2656 | numberToShow = numberString
2657 | end
2658 | end
2659 | end
2660 |
2661 | local scrollColor = optionsSpriteColor
2662 | if not configMenuInOptions then
2663 | scrollColor = optionsSpriteColorAlpha
2664 | end
2665 | if optionColor then
2666 | scrollColor = Color(optionColor[1], optionColor[2], optionColor[3], scrollColor.A, scrollColor.RO,
2667 | scrollColor.GO
2668 | , scrollColor.BO)
2669 | end
2670 |
2671 | local sliderString = "Slider1"
2672 | if useAltSlider then
2673 | sliderString = "Slider2"
2674 | end
2675 |
2676 | SliderSprite.Color = scrollColor
2677 | SliderSprite:SetFrame(sliderString, numberToShow)
2678 | SliderSprite:Render(lastOptionPos - Vector(scrollOffset, -2), vecZero, vecZero)
2679 | end
2680 |
2681 | local showStrikeout = thisOption.ShowStrikeout
2682 | if posOffset > 0 and (type(showStrikeout) == "boolean" and showStrikeout == true) or
2683 | (type(showStrikeout) == "function" and showStrikeout() == true) then
2684 | if configMenuInOptions then
2685 | StrikeOutSprite.Color = colorDefault
2686 | else
2687 | StrikeOutSprite.Color = colorHalf
2688 | end
2689 | StrikeOutSprite:SetFrame("Strikeout", math.floor(posOffset))
2690 | StrikeOutSprite:Render(lastOptionPos, vecZero, vecZero)
2691 | end
2692 | end
2693 |
2694 | --cursor
2695 | if cursorIsAtThisOption then
2696 | CursorSpriteRight:Render(lastOptionPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2697 | end
2698 | end
2699 |
2700 | --increase counter
2701 | renderedOptions = renderedOptions + 1
2702 |
2703 | --pos mod
2704 | lastOptionPos = lastOptionPos + Vector(0, 14)
2705 | end
2706 |
2707 | --render scroll arrows
2708 | if optionsCanScrollUp then
2709 | OptionsCursorSpriteUp:Render(centerPos + Vector(193, -86), vecZero, vecZero) --up arrow
2710 | end
2711 | if optionsCanScrollDown then
2712 | local yPos = 66
2713 | if shouldShowControls then
2714 | yPos = 40
2715 | end
2716 |
2717 | OptionsCursorSpriteDown:Render(centerPos + Vector(193, yPos), vecZero, vecZero) --down arrow
2718 | end
2719 | end
2720 |
2721 | MenuOverlaySprite:Render(centerPos, vecZero, vecZero)
2722 |
2723 | --title
2724 | local titleText = "Mod Config Menu"
2725 | if configMenuInSubcategory then
2726 | titleText = tostring(currentMenuCategory.Name)
2727 | end
2728 | local titleTextOffset = Font16Bold:GetStringWidthUTF8(titleText) / 2
2729 | Font16Bold:DrawString(titleText, titlePos.X - titleTextOffset, titlePos.Y - 9, mainFontColor, 0, true)
2730 |
2731 | --info
2732 | local infoTable = nil
2733 | local isOldInfo = false
2734 |
2735 | if configMenuInOptions then
2736 | if currentMenuOption and currentMenuOption.Info then
2737 | infoTable = currentMenuOption.Info
2738 | end
2739 | elseif configMenuInSubcategory then
2740 | if currentMenuSubcategory and currentMenuSubcategory.Info then
2741 | infoTable = currentMenuSubcategory.Info
2742 | end
2743 | elseif currentMenuCategory and currentMenuCategory.Info then
2744 | infoTable = currentMenuCategory.Info
2745 | if currentMenuCategory.IsOld then
2746 | isOldInfo = true
2747 | end
2748 | end
2749 |
2750 | if infoTable then
2751 | local lineWidth = 340
2752 | if shouldShowControls then
2753 | lineWidth = 260
2754 | end
2755 |
2756 | local infoTableDisplay = ModConfigMenu.ConvertDisplayToTextTable(infoTable, lineWidth, Font10)
2757 |
2758 | local lastInfoPos = infoPos - Vector(0, 6 * #infoTableDisplay)
2759 | for line = 1, #infoTableDisplay do
2760 | --text
2761 | local textToDraw = tostring(infoTableDisplay[line])
2762 | local posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2763 | local color = mainFontColor
2764 | if isOldInfo then
2765 | color = optionsFontColorTitle
2766 | end
2767 | Font10:DrawString(textToDraw, lastInfoPos.X - posOffset, lastInfoPos.Y - 6, color, 0, true)
2768 |
2769 | --pos mod
2770 | lastInfoPos = lastInfoPos + Vector(0, 10)
2771 | end
2772 | end
2773 |
2774 | --hud offset
2775 | if configMenuInOptions
2776 | and currentMenuOption
2777 | and currentMenuOption.ShowOffset
2778 | and ScreenHelper then
2779 | --render the visual
2780 | HudOffsetVisualBottomRight:Render(ScreenHelper.GetScreenBottomRight(), vecZero, vecZero)
2781 | HudOffsetVisualBottomLeft:Render(ScreenHelper.GetScreenBottomLeft(), vecZero, vecZero)
2782 | HudOffsetVisualTopRight:Render(ScreenHelper.GetScreenTopRight(), vecZero, vecZero)
2783 | HudOffsetVisualTopLeft:Render(ScreenHelper.GetScreenTopLeft(), vecZero, vecZero)
2784 | end
2785 |
2786 | --popup
2787 | if configMenuInPopup
2788 | and currentMenuOption
2789 | and (currentMenuOption.Popup or currentMenuOption.Restart or currentMenuOption.Rerun) then
2790 | PopupSprite:Render(centerPos, vecZero, vecZero)
2791 |
2792 | local popupTable = currentMenuOption.Popup
2793 |
2794 | if not popupTable then
2795 | if currentMenuOption.Restart then
2796 | popupTable = "Restart the game for this setting to take effect"
2797 | end
2798 |
2799 | if currentMenuOption.Rerun then
2800 | popupTable = "Start a new run for this setting to take effect"
2801 | end
2802 | end
2803 |
2804 | if popupTable then
2805 | local lineWidth = currentMenuOption.PopupWidth or 180
2806 |
2807 | local popupTableDisplay = ModConfigMenu.ConvertDisplayToTextTable(popupTable, lineWidth, Font10)
2808 |
2809 | local lastPopupPos = (centerPos + Vector(0, 2)) - Vector(0, 6 * #popupTableDisplay)
2810 | for line = 1, #popupTableDisplay do
2811 | --text
2812 | local textToDraw = tostring(popupTableDisplay[line])
2813 | local posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2814 | Font10:DrawString(textToDraw, lastPopupPos.X - posOffset, lastPopupPos.Y - 6, mainFontColor, 0, true)
2815 |
2816 | --pos mod
2817 | lastPopupPos = lastPopupPos + Vector(0, 10)
2818 | end
2819 | end
2820 | end
2821 |
2822 | --controls
2823 | if shouldShowControls then
2824 | --back
2825 | local bottomLeft = ScreenHelper.GetScreenBottomLeft(0)
2826 | if not configMenuInSubcategory then
2827 | CornerExit:Render(bottomLeft, vecZero, vecZero)
2828 | else
2829 | CornerBack:Render(bottomLeft, vecZero, vecZero)
2830 | end
2831 |
2832 | local goBackString = ""
2833 | if ModConfigMenu.Config.LastBackPressed then
2834 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed] then
2835 | goBackString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed]
2836 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed] then
2837 | goBackString = InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed]
2838 | end
2839 | end
2840 | Font10:DrawString(goBackString, (bottomLeft.X - Font10:GetStringWidthUTF8(goBackString) / 2) + 36,
2841 | bottomLeft.Y - 24,
2842 | mainFontColor, 0, true)
2843 |
2844 | --select
2845 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
2846 | if not configMenuInPopup then
2847 | local foundValidPopup = false
2848 | --[[
2849 | if configMenuInSubcategory
2850 | and configMenuInOptions
2851 | and currentMenuOption
2852 | and currentMenuOption.Type
2853 | and currentMenuOption.Type ~= ModConfigMenu.OptionType.SPACE
2854 | and currentMenuOption.Popup then
2855 | foundValidPopup = true
2856 | end
2857 | ]]
2858 | if foundValidPopup then
2859 | CornerOpen:Render(bottomRight, vecZero, vecZero)
2860 | else
2861 | CornerSelect:Render(bottomRight, vecZero, vecZero)
2862 | end
2863 |
2864 | local selectString = ""
2865 | if ModConfigMenu.Config.LastSelectPressed then
2866 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastSelectPressed] then
2867 | selectString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastSelectPressed]
2868 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastSelectPressed] then
2869 | selectString = InputHelper.ControllerToString[ModConfigMenu.Config.LastSelectPressed]
2870 | end
2871 | end
2872 | Font10:DrawString(selectString, (bottomRight.X - Font10:GetStringWidthUTF8(selectString) / 2) - 36,
2873 | bottomRight.Y - 24, mainFontColor, 0, true)
2874 | end
2875 | end
2876 | else
2877 | for i = 0, game:GetNumPlayers() - 1 do
2878 | local player = Isaac.GetPlayer(i)
2879 | local data = player:GetData()
2880 |
2881 | --enable player controls
2882 | if data.ConfigMenuPlayerPosition then
2883 | data.ConfigMenuPlayerPosition = nil
2884 | end
2885 | if data.ConfigMenuPlayerControlsDisabled then
2886 | player.ControlsEnabled = true
2887 | data.ConfigMenuPlayerControlsDisabled = false
2888 | end
2889 | end
2890 |
2891 | configMenuInSubcategory = false
2892 | configMenuInOptions = false
2893 | configMenuInPopup = false
2894 |
2895 | holdingCounterDown = 0
2896 | holdingCounterUp = 0
2897 | holdingCounterLeft = 0
2898 | holdingCounterRight = 0
2899 |
2900 | configMenuPositionCursorCategory = 1
2901 | configMenuPositionCursorSubcategory = 1
2902 | configMenuPositionCursorOption = 1
2903 |
2904 | configMenuPositionFirstSubcategory = 1
2905 |
2906 | leftCurrentOffset = 0
2907 | optionsCurrentOffset = 0
2908 | end
2909 | end
2910 |
2911 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_RENDER, ModConfigMenu.PostRender)
2912 |
2913 | function ModConfigMenu.OpenConfigMenu()
2914 | if ModConfigMenu.RoomIsSafe() then
2915 | if ModConfigMenu.Config["Mod Config Menu"].HideHudInMenu then
2916 | local game = Game()
2917 | if REPENTANCE then
2918 | local hud = game:GetHUD()
2919 | hud:SetVisible(false)
2920 | else
2921 | local seeds = game:GetSeeds()
2922 | seeds:AddSeedEffect(SeedEffect.SEED_NO_HUD)
2923 | end
2924 | end
2925 |
2926 | ModConfigMenu.IsVisible = true
2927 | else
2928 | local sfx = SFXManager()
2929 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
2930 | end
2931 | end
2932 |
2933 | function ModConfigMenu.CloseConfigMenu()
2934 | ModConfigMenu.LeavePopup()
2935 | ModConfigMenu.LeaveOptions()
2936 | ModConfigMenu.LeaveSubcategory()
2937 |
2938 | local game = Game()
2939 | if REPENTANCE then
2940 | local hud = game:GetHUD()
2941 | hud:SetVisible(true)
2942 | else
2943 | local seeds = game:GetSeeds()
2944 | seeds:RemoveSeedEffect(SeedEffect.SEED_NO_HUD)
2945 | end
2946 |
2947 |
2948 | ModConfigMenu.IsVisible = false
2949 | end
2950 |
2951 | function ModConfigMenu.ToggleConfigMenu()
2952 | if ModConfigMenu.IsVisible then
2953 | ModConfigMenu.CloseConfigMenu()
2954 | else
2955 | ModConfigMenu.OpenConfigMenu()
2956 | end
2957 | end
2958 |
2959 | function ModConfigMenu.InputAction(_, entity, inputHook, buttonAction)
2960 | if ModConfigMenu.IsVisible and buttonAction ~= ButtonAction.ACTION_FULLSCREEN and
2961 | buttonAction ~= ButtonAction.ACTION_CONSOLE then
2962 | if inputHook == InputHook.IS_ACTION_PRESSED or inputHook == InputHook.IS_ACTION_TRIGGERED then
2963 | return false
2964 | else
2965 | return 0
2966 | end
2967 | end
2968 | end
2969 |
2970 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_INPUT_ACTION, ModConfigMenu.InputAction)
2971 |
2972 | --console commands that toggle the menu
2973 | local toggleCommands = {
2974 | ["modconfigmenu"] = true,
2975 | ["modconfig"] = true,
2976 | ["mcm"] = true,
2977 | ["mc"] = true
2978 | }
2979 | function ModConfigMenu.ExecuteCmd(_, command, args)
2980 | command = command:lower()
2981 |
2982 | if toggleCommands[command] then
2983 | ModConfigMenu.ToggleConfigMenu()
2984 | end
2985 | end
2986 |
2987 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_EXECUTE_CMD, ModConfigMenu.ExecuteCmd)
2988 |
2989 | if ModConfigMenu.StandaloneMod then
2990 | if not ModConfigMenu.StandaloneSaveLoaded then
2991 | ModConfigMenu.StandaloneSaveLoaded = true
2992 | end
2993 | end
2994 |
2995 | ------------
2996 | --FINISHED--
2997 | ------------
2998 | Isaac.DebugString("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loaded.")
2999 | print("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loaded.")
3000 |
3001 | return ModConfigMenu
3002 |
--------------------------------------------------------------------------------
/scripts/screenhelper.lua:
--------------------------------------------------------------------------------
1 | ------------------------------------------------------------------------------
2 | -- IMPORTANT: DO NOT EDIT THIS FILE!!! --
3 | ------------------------------------------------------------------------------
4 | -- This file relies on other versions of itself being the same. --
5 | -- If you need something in this file changed, please let the creator know! --
6 | ------------------------------------------------------------------------------
7 |
8 | -------------
9 | -- version --
10 | -------------
11 |
12 | local fileVersion = 1
13 |
14 | --prevent older/same version versions of this script from loading
15 | if ScreenHelper and ScreenHelper.Version >= fileVersion then
16 | return ScreenHelper
17 | end
18 |
19 | if not ScreenHelper then
20 | ScreenHelper = {}
21 | ScreenHelper.Version = fileVersion
22 | elseif ScreenHelper.Version < fileVersion then
23 | local oldVersion = ScreenHelper.Version
24 |
25 | -- handle old versions
26 |
27 | ScreenHelper.Version = fileVersion
28 | end
29 |
30 | ---------------------
31 | --hud offset helper--
32 | ---------------------
33 |
34 | ScreenHelper.CurrentScreenOffset = ScreenHelper.CurrentScreenOffset or 0
35 |
36 | function ScreenHelper.SetOffset(num)
37 | num = math.min(math.max(math.floor(num), 0), 10)
38 |
39 | ScreenHelper.CurrentScreenOffset = num
40 |
41 | return num
42 | end
43 |
44 | function ScreenHelper.GetOffset()
45 | return ScreenHelper.CurrentScreenOffset
46 | end
47 |
48 | ------------------------------------
49 | --screen size and corner functions--
50 | ------------------------------------
51 |
52 | local vecZero = Vector(0, 0)
53 | function ScreenHelper.GetScreenSize()
54 | if REPENTANCE then
55 | local screenWidth = Isaac.GetScreenWidth()
56 | local screenHeight = Isaac.GetScreenHeight()
57 |
58 | return Vector(screenWidth, screenHeight)
59 | else --based off of code from kilburn
60 | local game = Game()
61 | local room = game:GetRoom()
62 |
63 | local pos = room:WorldToScreenPosition(vecZero) - room:GetRenderScrollOffset() - game.ScreenShakeOffset
64 |
65 | local rx = pos.X + 60 * 26 / 40
66 | local ry = pos.Y + 140 * (26 / 40)
67 |
68 | return Vector(rx * 2 + 13 * 26, ry * 2 + 7 * 26)
69 | end
70 | end
71 |
72 | function ScreenHelper.GetScreenCenter()
73 | return ScreenHelper.GetScreenSize() / 2
74 | end
75 |
76 | function ScreenHelper.GetScreenBottomRight(offset)
77 | offset = offset or ScreenHelper.GetOffset()
78 |
79 | local pos = ScreenHelper.GetScreenSize()
80 | local hudOffset = Vector( -offset * 2.2, -offset * 1.6)
81 | pos = pos + hudOffset
82 |
83 | return pos
84 | end
85 |
86 | function ScreenHelper.GetScreenBottomLeft(offset)
87 | offset = offset or ScreenHelper.GetOffset()
88 |
89 | local pos = Vector(0, ScreenHelper.GetScreenBottomRight(0).Y)
90 | local hudOffset = Vector(offset * 2.2, -offset * 1.6)
91 | pos = pos + hudOffset
92 |
93 | return pos
94 | end
95 |
96 | function ScreenHelper.GetScreenTopRight(offset)
97 | offset = offset or ScreenHelper.GetOffset()
98 |
99 | local pos = Vector(ScreenHelper.GetScreenBottomRight(0).X, 0)
100 | local hudOffset = Vector( -offset * 2.2, offset * 1.2)
101 | pos = pos + hudOffset
102 |
103 | return pos
104 | end
105 |
106 | function ScreenHelper.GetScreenTopLeft(offset)
107 | offset = offset or ScreenHelper.GetOffset()
108 |
109 | local pos = vecZero
110 | local hudOffset = Vector(offset * 2, offset * 1.2)
111 | pos = pos + hudOffset
112 |
113 | return pos
114 | end
115 |
116 | return ScreenHelper
117 |
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210110125025_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210110125025_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160746_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160746_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160751_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160751_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160753_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160753_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160757_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160757_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160811_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160811_1.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210113160813_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/screenshots/1603631350_preview_250900_20210113160813_1.png
--------------------------------------------------------------------------------
/steam/thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/7a707788f58fe57eb03da7ffe211e78ba45db6c8/steam/thumbnail.jpg
--------------------------------------------------------------------------------