├── steam
├── thumbnail.jpg
└── 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
├── .gitignore
├── resources
├── gfx
│ └── ui
│ │ └── modconfig
│ │ ├── black.png
│ │ ├── menu.png
│ │ ├── arrows.png
│ │ ├── offset.png
│ │ ├── slider.png
│ │ ├── strike.png
│ │ ├── backselect.png
│ │ ├── menu_overlay.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
│ │ └── menu.anm2
└── previewfile_1603631350.jpg
├── .gitattributes
├── main.lua
├── release.sh
├── .vscode
├── extensions.json
└── settings.json
├── copy-metadata.py
├── LICENSE
├── metadata.xml
├── .github
└── workflows
│ └── ci.yml
├── cspell.json
├── release.py
├── scripts
├── screenhelper.lua
├── inputhelper.lua
└── modconfig.lua
├── docs
├── quick-start.md
└── api.md
└── README.md
/steam/thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/steam/thumbnail.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OS-specific artifacts
2 | .DS_Store
3 | thumbs.db
4 |
5 | # Isaac-related files
6 | disable.it
7 | *.dat
8 |
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/black.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/menu.png
--------------------------------------------------------------------------------
/resources/previewfile_1603631350.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/previewfile_1603631350.jpg
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/arrows.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/offset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/offset.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/slider.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/slider.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/strike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/strike.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/backselect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/backselect.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/menu_overlay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/resources/gfx/ui/modconfig/menu_overlay.png
--------------------------------------------------------------------------------
/resources/gfx/ui/modconfig/popup_thin_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/resources/gfx/ui/modconfig/popup_wide_small.png
--------------------------------------------------------------------------------
/steam/screenshots/1603631350_preview_250900_20210110125025_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Zamiell/isaac-mod-config-menu/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/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/HEAD/steam/screenshots/1603631350_preview_250900_20210113160813_1.png
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 113
23 | Public
24 |
25 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/scripts/modconfig.lua:
--------------------------------------------------------------------------------
1 | -- Imports
2 | local json = require("json")
3 |
4 | local IS_DEV = false
5 |
6 | -------------
7 | -- version --
8 | -------------
9 |
10 | -- The final version of Chifilly's Mod Config Menu fork was 33.
11 | -- For the pure version, we arbitrarily selected a starting point of 100 and incremented from there.
12 | local VERSION = 113
13 |
14 | ModConfigMenu = {}
15 | ModConfigMenu.Version = VERSION
16 |
17 | ---------------------
18 | -- local functinos --
19 | ---------------------
20 |
21 | local function isRepentancePlusInstalled()
22 | local game = Game()
23 | local room = game:GetRoom()
24 | local metatable = getmetatable(room)
25 | return type(metatable) == "table" and type(metatable.DamageGridWithSource) == "function"
26 | end
27 | local IS_REPENTANCE_PLUS = isRepentancePlusInstalled()
28 |
29 | -------------------------
30 | --SAVE HELPER FUNCTIONS--
31 | -------------------------
32 |
33 | local SaveHelper = {}
34 |
35 | function SaveHelper.CopyTable(tableToCopy)
36 | local table2 = {}
37 |
38 | for i, value in pairs(tableToCopy) do
39 | if type(value) == "table" then
40 | table2[i] = SaveHelper.CopyTable(value)
41 | else
42 | table2[i] = value
43 | end
44 | end
45 |
46 | return table2
47 | end
48 |
49 | function SaveHelper.FillTable(tableToFill, tableToFillFrom)
50 | for i, value in pairs(tableToFillFrom) do
51 | if tableToFill[i] ~= nil then
52 | if type(value) == "table" then
53 | if type(tableToFill[i]) ~= "table" then
54 | tableToFill[i] = {}
55 | end
56 |
57 | tableToFill[i] = SaveHelper.FillTable(tableToFill[i], value)
58 | else
59 | tableToFill[i] = value
60 | end
61 | else
62 | if type(value) == "table" then
63 | if type(tableToFill[i]) ~= "table" then
64 | tableToFill[i] = {}
65 | end
66 |
67 | tableToFill[i] = SaveHelper.FillTable({}, value)
68 | else
69 | tableToFill[i] = value
70 | end
71 | end
72 | end
73 |
74 | return tableToFill
75 | end
76 |
77 | -----------
78 | -- setup --
79 | -----------
80 |
81 | Isaac.DebugString("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loading...")
82 |
83 | local vecZero = Vector(0, 0)
84 | local restartWarnMessage = nil
85 | local rerunWarnMessage = nil
86 |
87 | if not InputHelper then
88 | require("scripts.inputhelper")
89 | if not InputHelper then
90 | error("Mod Config Menu requires Input Helper to function", 2)
91 | end
92 | end
93 |
94 | if not ScreenHelper then
95 | require("scripts.screenhelper")
96 | if not ScreenHelper then
97 | error("Mod Config Menu requires Screen Helper to function", 2)
98 | end
99 | end
100 |
101 | ModConfigMenu.Mod = RegisterMod("Mod Config Menu", 1)
102 |
103 | ----------
104 | --SAVING--
105 | ----------
106 |
107 | ModConfigMenu.ConfigDefault = {}
108 | ModConfigMenu.Config = {}
109 |
110 | ---------------------------------
111 | -- Saving and loading MCM data --
112 | ---------------------------------
113 |
114 | local function save()
115 | local mod = ModConfigMenu.Mod
116 |
117 | if IS_DEV then
118 | Isaac.DebugString("MCM is saving data.")
119 | end
120 |
121 | local jsonString = json.encode(ModConfigMenu.Config)
122 | if jsonString == nil or jsonString == "" then
123 | return
124 | end
125 |
126 | mod:SaveData(jsonString)
127 | end
128 |
129 | local function load()
130 | local mod = ModConfigMenu.Mod
131 |
132 | if IS_DEV then
133 | Isaac.DebugString("MCM is loading data.")
134 | end
135 |
136 | if not mod:HasData() then
137 | save()
138 | return
139 | end
140 |
141 | local jsonString = mod:LoadData()
142 | if jsonString == nil or jsonString == "" or jsonString == "[]" or jsonString == "{}" then
143 | save()
144 | return
145 | end
146 |
147 | local data = nil
148 | local function loadJSON()
149 | data = json.decode(jsonString)
150 | end
151 |
152 | if not pcall(loadJSON) or data == nil then
153 | Isaac.DebugString("Error: Failed to load Mod Config Menu data from the \"save#.dat\" file.")
154 | return
155 | end
156 |
157 | ModConfigMenu.LoadSave(data)
158 | end
159 |
160 | function ModConfigMenu.LoadSave(data)
161 | local saveData = SaveHelper.CopyTable(ModConfigMenu.ConfigDefault)
162 | if type(data) == "table" then
163 | saveData["General"] = SaveHelper.FillTable(saveData["General"], data["General"] or {})
164 | saveData["Mod Config Menu"] = SaveHelper.FillTable(saveData["Mod Config Menu"], data["Mod Config Menu"] or data or {})
165 | if IS_DEV then
166 | Isaac.DebugString("Successfully loaded data from the \"save#.dat\" file.")
167 | end
168 | end
169 |
170 | local currentData = SaveHelper.CopyTable(ModConfigMenu.Config)
171 | saveData = SaveHelper.FillTable(currentData, saveData)
172 |
173 | ModConfigMenu.Config = SaveHelper.CopyTable(saveData)
174 |
175 | --make sure ScreenHelper's offset matches MCM's offset
176 | if ScreenHelper then
177 | ScreenHelper.SetOffset(ModConfigMenu.Config["General"].HudOffset)
178 | end
179 | end
180 |
181 | local function vanillaMCMOptionRegisterSave(mcmOption)
182 | local oldOnChange = mcmOption.OnChange
183 | mcmOption.OnChange = function(currentValue)
184 | local value = oldOnChange(currentValue)
185 | save()
186 | return value
187 | end
188 | end
189 |
190 | --------------
191 | --game start--
192 | --------------
193 | local versionPrintFont = Font()
194 | versionPrintFont:Load("font/pftempestasevencondensed.fnt")
195 |
196 | local versionPrintTimer = 0
197 | local isFirstRun = true
198 |
199 | --returns true if the room is clear and there are no active enemies and there are no projectiles
200 | ModConfigMenu.IgnoreActiveEnemies = ModConfigMenu.IgnoreActiveEnemies or {}
201 | function ModConfigMenu.RoomIsSafe()
202 | local roomHasDanger = false
203 |
204 | for _, entity in pairs(Isaac.GetRoomEntities()) do
205 | if (
206 | entity:IsActiveEnemy()
207 | and not entity:HasEntityFlags(EntityFlag.FLAG_FRIENDLY)
208 | and (
209 | not ModConfigMenu.IgnoreActiveEnemies[entity.Type]
210 | or (
211 | ModConfigMenu.IgnoreActiveEnemies[entity.Type]
212 | and not ModConfigMenu.IgnoreActiveEnemies[entity.Type][-1]
213 | and not ModConfigMenu.IgnoreActiveEnemies[entity.Type][entity.Variant]
214 | )
215 | )
216 | ) then
217 | roomHasDanger = true
218 | elseif (
219 | entity.Type == EntityType.ENTITY_PROJECTILE
220 | and entity:ToProjectile().ProjectileFlags & ProjectileFlags.CANT_HIT_PLAYER ~= 1
221 | ) then
222 | roomHasDanger = true
223 | elseif entity.Type == EntityType.ENTITY_BOMBDROP then
224 | roomHasDanger = true
225 | end
226 | end
227 |
228 | local game = Game()
229 | local room = game:GetRoom()
230 |
231 | if room:IsClear() and not roomHasDanger then
232 | return true
233 | end
234 |
235 | return false
236 | end
237 |
238 | ModConfigMenu.IsVisible = false
239 | function ModConfigMenu.PostGameStarted()
240 | if IS_DEV then
241 | Isaac.DebugString("POST_GAME_STARTED")
242 | end
243 |
244 | rerunWarnMessage = nil
245 |
246 | load()
247 |
248 | if not isFirstRun then
249 | return
250 | end
251 | isFirstRun = false
252 |
253 | if ModConfigMenu.Config["Mod Config Menu"].ShowControls and isFirstRun then
254 | versionPrintTimer = 120
255 | end
256 |
257 | ModConfigMenu.IsVisible = false
258 |
259 | -- Add Potato Dummy to the ignore list.
260 | local potatoType = Isaac.GetEntityTypeByName("Potato Dummy")
261 | local potatoVariant = Isaac.GetEntityVariantByName("Potato Dummy")
262 |
263 | if potatoType and potatoVariant > 0 then
264 | ModConfigMenu.IgnoreActiveEnemies[potatoType] = ModConfigMenu.IgnoreActiveEnemies or {}
265 | ModConfigMenu.IgnoreActiveEnemies[potatoType][potatoVariant] = true
266 | end
267 | end
268 |
269 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_GAME_STARTED, ModConfigMenu.PostGameStarted)
270 |
271 |
272 | ---------------
273 | --post update--
274 | ---------------
275 | function ModConfigMenu.PostUpdate()
276 | if versionPrintTimer > 0 then
277 | versionPrintTimer = versionPrintTimer - 1
278 | end
279 | end
280 |
281 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_UPDATE, ModConfigMenu.PostUpdate)
282 |
283 |
284 | ------------------------------------
285 | --set up the menu sprites and font--
286 | ------------------------------------
287 | function ModConfigMenu.GetMenuAnm2Sprite(animation, frame, color)
288 | local sprite = Sprite()
289 |
290 | sprite:Load("gfx/ui/modconfig/menu.anm2", true)
291 | sprite:SetFrame(animation or "Idle", frame or 0)
292 |
293 | if color then
294 | sprite.Color = color
295 | end
296 |
297 | return sprite
298 | end
299 |
300 | --main menu sprites
301 | local MenuSprite = ModConfigMenu.GetMenuAnm2Sprite("Idle", 0)
302 | local MenuOverlaySprite = ModConfigMenu.GetMenuAnm2Sprite("IdleOverlay", 0)
303 | local PopupSprite = ModConfigMenu.GetMenuAnm2Sprite("Popup", 0)
304 |
305 | --main cursors
306 | local CursorSpriteRight = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 0)
307 | local CursorSpriteUp = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 1)
308 | local CursorSpriteDown = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 2)
309 |
310 | --colors
311 | local colorDefault = Color(1, 1, 1, 1, 0, 0, 0)
312 | local colorHalf = Color(1, 1, 1, 0.5, 0, 0, 0)
313 |
314 | --subcategory pane cursors
315 | local SubcategoryCursorSpriteLeft = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 3, colorHalf)
316 | local SubcategoryCursorSpriteRight = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 0, colorHalf)
317 |
318 | --options pane cursors
319 | local OptionsCursorSpriteUp = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 1, colorHalf)
320 | local OptionsCursorSpriteDown = ModConfigMenu.GetMenuAnm2Sprite("Cursor", 2, colorHalf)
321 |
322 | --other options pane objects
323 | local SubcategoryDividerSprite = ModConfigMenu.GetMenuAnm2Sprite("Divider", 0, colorHalf)
324 | local SliderSprite = ModConfigMenu.GetMenuAnm2Sprite("Slider1", 0)
325 |
326 | --strikeout
327 | local StrikeOutSprite = ModConfigMenu.GetMenuAnm2Sprite("Strikeout", 0)
328 |
329 | --back/select corner papers
330 | local CornerSelect = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 0)
331 | local CornerBack = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 1)
332 | local CornerOpen = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 2)
333 | local CornerExit = ModConfigMenu.GetMenuAnm2Sprite("BackSelect", 3)
334 |
335 | --fonts
336 | local Font10 = Font()
337 | if IS_REPENTANCE_PLUS then
338 | Font10:Load("font/teammeatex/teammeatex10.fnt")
339 | else
340 | Font10:Load("font/teammeatfont10.fnt")
341 | end
342 |
343 | local Font12 = Font()
344 | if IS_REPENTANCE_PLUS then
345 | Font12:Load("font/teammeatex/teammeatex12.fnt")
346 | else
347 | Font12:Load("font/teammeatfont12.fnt")
348 | end
349 |
350 | local Font16Bold = Font()
351 | Font16Bold:Load("font/teammeatfont16bold.fnt")
352 |
353 | --popups
354 | ModConfigMenu.PopupGfx = ModConfigMenu.PopupGfx or {}
355 | ModConfigMenu.PopupGfx.THIN_SMALL = "gfx/ui/modconfig/popup_thin_small.png"
356 | ModConfigMenu.PopupGfx.THIN_MEDIUM = "gfx/ui/modconfig/popup_thin_medium.png"
357 | ModConfigMenu.PopupGfx.THIN_LARGE = "gfx/ui/modconfig/popup_thin_large.png"
358 | ModConfigMenu.PopupGfx.WIDE_SMALL = "gfx/ui/modconfig/popup_wide_small.png"
359 | ModConfigMenu.PopupGfx.WIDE_MEDIUM = "gfx/ui/modconfig/popup_wide_medium.png"
360 | ModConfigMenu.PopupGfx.WIDE_LARGE = "gfx/ui/modconfig/popup_wide_large.png"
361 |
362 |
363 | -------------------------
364 | --add setting functions--
365 | -------------------------
366 | ModConfigMenu.OptionType = ModConfigMenu.OptionType or {}
367 | ModConfigMenu.OptionType.TEXT = 1
368 | ModConfigMenu.OptionType.SPACE = 2
369 | ModConfigMenu.OptionType.SCROLL = 3
370 | ModConfigMenu.OptionType.BOOLEAN = 4
371 | ModConfigMenu.OptionType.NUMBER = 5
372 | ModConfigMenu.OptionType.KEYBIND_KEYBOARD = 6
373 | ModConfigMenu.OptionType.KEYBIND_CONTROLLER = 7
374 | ModConfigMenu.OptionType.TITLE = 8
375 |
376 | ModConfigMenu.MenuData = ModConfigMenu.MenuData or {}
377 |
378 | --CATEGORY FUNCTIONS
379 | function ModConfigMenu.GetCategoryIDByName(categoryName)
380 | if type(categoryName) ~= "string" then
381 | return categoryName
382 | end
383 |
384 | local categoryID = nil
385 |
386 | for i = 1, #ModConfigMenu.MenuData do
387 | if categoryName == ModConfigMenu.MenuData[i].Name then
388 | categoryID = i
389 | break
390 | end
391 | end
392 |
393 | return categoryID
394 | end
395 |
396 | function ModConfigMenu.UpdateCategory(categoryName, dataTable)
397 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
398 | error("ModConfigMenu.UpdateCategory - No valid category name provided", 2)
399 | end
400 |
401 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
402 | if categoryID == nil then
403 | categoryID = #ModConfigMenu.MenuData + 1
404 | ModConfigMenu.MenuData[categoryID] = {}
405 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
406 | end
407 |
408 | if type(categoryName) == "string" or dataTable.Name then
409 | ModConfigMenu.MenuData[categoryID].Name = dataTable.Name or categoryName
410 | end
411 |
412 | if dataTable.Info then
413 | ModConfigMenu.MenuData[categoryID].Info = dataTable.Info
414 | end
415 |
416 | if dataTable.IsOld then
417 | ModConfigMenu.MenuData[categoryID].IsOld = dataTable.IsOld
418 | end
419 | end
420 |
421 | function ModConfigMenu.SetCategoryInfo(categoryName, info)
422 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
423 | error("ModConfigMenu.SetCategoryInfo - No valid category name provided", 2)
424 | end
425 |
426 | ModConfigMenu.UpdateCategory(categoryName, {
427 | Info = info
428 | })
429 | end
430 |
431 | function ModConfigMenu.RemoveCategory(categoryName)
432 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
433 | error("ModConfigMenu.RemoveCategory - No valid category name provided", 2)
434 | end
435 |
436 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
437 | if categoryID then
438 | table.remove(ModConfigMenu.MenuData, categoryID)
439 | return true
440 | end
441 |
442 | return false
443 | end
444 |
445 | --SUBCATEGORY FUNCTIONS
446 | function ModConfigMenu.GetSubcategoryIDByName(categoryName, subcategoryName)
447 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
448 | error("ModConfigMenu.GetSubcategoryIDByName - No valid category name provided", 2)
449 | end
450 |
451 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
452 |
453 | if type(subcategoryName) ~= "string" then
454 | return subcategoryName
455 | end
456 |
457 | local subcategoryID = nil
458 |
459 | for i = 1, #ModConfigMenu.MenuData[categoryID].Subcategories do
460 | if subcategoryName == ModConfigMenu.MenuData[categoryID].Subcategories[i].Name then
461 | subcategoryID = i
462 | break
463 | end
464 | end
465 |
466 | return subcategoryID
467 | end
468 |
469 | function ModConfigMenu.UpdateSubcategory(categoryName, subcategoryName, dataTable)
470 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
471 | error("ModConfigMenu.UpdateSubcategory - No valid category name provided", 2)
472 | end
473 |
474 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
475 | error("ModConfigMenu.UpdateSubcategory - No valid subcategory name provided", 2)
476 | end
477 |
478 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
479 | if categoryID == nil then
480 | categoryID = #ModConfigMenu.MenuData + 1
481 | ModConfigMenu.MenuData[categoryID] = {}
482 | ModConfigMenu.MenuData[categoryID].Name = tostring(categoryName)
483 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
484 | end
485 |
486 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
487 | if subcategoryID == nil then
488 | subcategoryID = #ModConfigMenu.MenuData[categoryID].Subcategories + 1
489 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID] = {}
490 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options = {}
491 | end
492 |
493 | if type(subcategoryName) == "string" or dataTable.Name then
494 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Name = dataTable.Name or subcategoryName
495 | end
496 |
497 | if dataTable.Info then
498 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Info = dataTable.Info
499 | end
500 | end
501 |
502 | function ModConfigMenu.RemoveSubcategory(categoryName, subcategoryName)
503 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
504 | error("ModConfigMenu.RemoveSubcategory - No valid category name provided", 2)
505 | end
506 |
507 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
508 | error("ModConfigMenu.RemoveSubcategory - No valid subcategory name provided", 2)
509 | end
510 |
511 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
512 | if categoryID then
513 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
514 | if subcategoryID then
515 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories, subcategoryID)
516 | return true
517 | end
518 | end
519 |
520 | return false
521 | end
522 |
523 | --SETTING FUNCTIONS
524 | function ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
525 | if settingTable == nil then
526 | settingTable = subcategoryName
527 | subcategoryName = nil
528 | end
529 |
530 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
531 | error("ModConfigMenu.AddSetting - No valid category name provided", 2)
532 | end
533 |
534 | subcategoryName = subcategoryName or "Uncategorized"
535 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
536 | error("ModConfigMenu.AddSetting - No valid subcategory name provided", 2)
537 | end
538 |
539 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
540 | if categoryID == nil then
541 | categoryID = #ModConfigMenu.MenuData + 1
542 | ModConfigMenu.MenuData[categoryID] = {}
543 | ModConfigMenu.MenuData[categoryID].Name = tostring(categoryName)
544 | ModConfigMenu.MenuData[categoryID].Subcategories = {}
545 | end
546 |
547 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
548 | if subcategoryID == nil then
549 | subcategoryID = #ModConfigMenu.MenuData[categoryID].Subcategories + 1
550 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID] = {}
551 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Name = tostring(subcategoryName)
552 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options = {}
553 | end
554 |
555 | ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[
556 | #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options + 1] = settingTable
557 |
558 | return settingTable
559 | end
560 |
561 | function ModConfigMenu.AddText(categoryName, subcategoryName, text, color)
562 | if color == nil and type(text) ~= "string" and type(text) ~= "function" then
563 | color = text
564 | text = subcategoryName
565 | subcategoryName = nil
566 | end
567 |
568 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
569 | error("ModConfigMenu.AddText - No valid category name provided", 2)
570 | end
571 |
572 | subcategoryName = subcategoryName or "Uncategorized"
573 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
574 | error("ModConfigMenu.AddText - No valid subcategory name provided", 2)
575 | end
576 |
577 | local settingTable = {
578 | Type = ModConfigMenu.OptionType.TEXT,
579 | Display = text,
580 | Color = color,
581 | NoCursorHere = true
582 | }
583 |
584 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
585 | end
586 |
587 | function ModConfigMenu.AddTitle(categoryName, subcategoryName, text, color)
588 | if color == nil and type(text) ~= "string" and type(text) ~= "function" then
589 | color = text
590 | text = subcategoryName
591 | subcategoryName = nil
592 | end
593 |
594 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
595 | error("ModConfigMenu.AddTitle - No valid category name provided", 2)
596 | end
597 |
598 | subcategoryName = subcategoryName or "Uncategorized"
599 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
600 | error("ModConfigMenu.AddTitle - No valid subcategory name provided", 2)
601 | end
602 |
603 | local settingTable = {
604 | Type = ModConfigMenu.OptionType.TITLE,
605 | Display = text,
606 | Color = color,
607 | NoCursorHere = true
608 | }
609 |
610 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
611 | end
612 |
613 | function ModConfigMenu.AddSpace(categoryName, subcategoryName)
614 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
615 | error("ModConfigMenu.AddSpace - No valid category name provided", 2)
616 | end
617 |
618 | subcategoryName = subcategoryName or "Uncategorized"
619 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
620 | error("ModConfigMenu.AddSpace - No valid subcategory name provided", 2)
621 | end
622 |
623 | local settingTable = {
624 | Type = ModConfigMenu.OptionType.SPACE
625 | }
626 |
627 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
628 | end
629 |
630 | local altSlider = false
631 | function ModConfigMenu.SimpleAddSetting(settingType, categoryName, subcategoryName, configTableAttribute, minValue,
632 | maxValue, modifyBy, defaultValue, displayText, displayValueProxies, displayDevice
633 | , info, color, functionName)
634 | --set default values
635 | if defaultValue == nil then
636 | if settingType == ModConfigMenu.OptionType.BOOLEAN then
637 | defaultValue = false
638 | else
639 | defaultValue = 0
640 | end
641 | end
642 |
643 | if settingType == ModConfigMenu.OptionType.NUMBER then
644 | minValue = minValue or 0
645 | maxValue = maxValue or 10
646 | modifyBy = modifyBy or 1
647 | else
648 | minValue = nil
649 | maxValue = nil
650 | modifyBy = nil
651 | end
652 |
653 | functionName = functionName or "SimpleAddSetting"
654 |
655 | --erroring
656 | if categoryName == nil then
657 | error("ModConfigMenu." .. tostring(functionName) .. " - No valid category name provided", 2)
658 | end
659 | if configTableAttribute == nil then
660 | error("ModConfigMenu." .. tostring(functionName) .. " - No valid config table attribute provided", 2)
661 | end
662 |
663 | --create config value
664 | ModConfigMenu.Config[categoryName] = ModConfigMenu.Config[categoryName] or {}
665 | if ModConfigMenu.Config[categoryName][configTableAttribute] == nil then
666 | ModConfigMenu.Config[categoryName][configTableAttribute] = defaultValue
667 | end
668 |
669 | ModConfigMenu.ConfigDefault[categoryName] = ModConfigMenu.ConfigDefault[categoryName] or {}
670 | if ModConfigMenu.ConfigDefault[categoryName][configTableAttribute] == nil then
671 | ModConfigMenu.ConfigDefault[categoryName][configTableAttribute] = defaultValue
672 | end
673 |
674 | --setting
675 | local settingTable = {
676 | Type = settingType,
677 | Attribute = configTableAttribute,
678 | CurrentSetting = function()
679 | return ModConfigMenu.Config[categoryName][configTableAttribute]
680 | end,
681 | Default = defaultValue,
682 | Display = function(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
683 | local currentValue = ModConfigMenu.Config[categoryName][configTableAttribute]
684 |
685 | local displayString = ""
686 |
687 | if displayText then
688 | displayString = displayText .. ": "
689 | end
690 |
691 | if settingType == ModConfigMenu.OptionType.SCROLL then
692 | displayString = displayString .. "$scroll" .. tostring(math.floor(currentValue))
693 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
694 | local key = "None"
695 |
696 | if currentValue > -1 then
697 | key = "Unknown Key"
698 |
699 | if InputHelper.KeyboardToString[currentValue] then
700 | key = InputHelper.KeyboardToString[currentValue]
701 | end
702 | end
703 |
704 | displayString = displayString .. key
705 |
706 | if displayDevice then
707 | displayString = displayString .. " (keyboard)"
708 | end
709 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
710 | local key = "None"
711 |
712 | if currentValue > -1 then
713 | key = "Unknown Button"
714 |
715 | if InputHelper.ControllerToString[currentValue] then
716 | key = InputHelper.ControllerToString[currentValue]
717 | end
718 | end
719 |
720 | displayString = displayString .. key
721 |
722 | if displayDevice then
723 | displayString = displayString .. " (controller)"
724 | end
725 | elseif displayValueProxies and displayValueProxies[currentValue] then
726 | displayString = displayString .. tostring(displayValueProxies[currentValue])
727 | else
728 | displayString = displayString .. tostring(currentValue)
729 | end
730 |
731 | return displayString
732 | end,
733 | OnChange = function(currentValue)
734 | if not currentValue then
735 | if settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
736 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
737 | currentValue = -1
738 | end
739 | end
740 |
741 | ModConfigMenu.Config[categoryName][configTableAttribute] = currentValue
742 | end,
743 | Info = info,
744 | Color = color
745 | }
746 |
747 | if settingType == ModConfigMenu.OptionType.NUMBER then
748 | settingTable.Minimum = minValue
749 | settingTable.Maximum = maxValue
750 | settingTable.ModifyBy = modifyBy
751 | elseif settingType == ModConfigMenu.OptionType.SCROLL then
752 | settingTable.AltSlider = altSlider
753 | altSlider = not altSlider
754 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
755 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
756 | settingTable.PopupGfx = ModConfigMenu.PopupGfx.WIDE_SMALL
757 | settingTable.PopupWidth = 280
758 | settingTable.Popup = function()
759 | local currentValue = ModConfigMenu.Config[categoryName][configTableAttribute]
760 |
761 | local goBackString = "back"
762 | if ModConfigMenu.Config.LastBackPressed then
763 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed] then
764 | goBackString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed]
765 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed] then
766 | goBackString = InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed]
767 | end
768 | end
769 |
770 | local keepSettingString = ""
771 | if currentValue > -1 then
772 | local currentSettingString = nil
773 | if (settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD and InputHelper.KeyboardToString[currentValue]) then
774 | currentSettingString = InputHelper.KeyboardToString[currentValue]
775 | elseif (
776 | settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER and InputHelper.ControllerToString[currentValue]) then
777 | currentSettingString = InputHelper.ControllerToString[currentValue]
778 | end
779 |
780 | keepSettingString = "This setting is currently set to \"" ..
781 | currentSettingString .. "\".$newlinePress this button to keep it unchanged.$newline$newline"
782 | end
783 |
784 | local deviceString = ""
785 | if settingType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
786 | deviceString = "keyboard"
787 | elseif settingType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
788 | deviceString = "controller"
789 | end
790 |
791 | return "Press a button on your " ..
792 | deviceString ..
793 | " to change this setting.$newline$newline" ..
794 | keepSettingString .. "Press \"" .. goBackString .. "\" to go back and clear this setting."
795 | end
796 | end
797 |
798 | return ModConfigMenu.AddSetting(categoryName, subcategoryName, settingTable)
799 | end
800 |
801 | function ModConfigMenu.AddBooleanSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText,
802 | displayValueProxies, info, color)
803 | --move args around
804 | if type(configTableAttribute) ~= "string" then
805 | color = info
806 | info = displayValueProxies
807 | displayValueProxies = displayText
808 | displayText = defaultValue
809 | defaultValue = configTableAttribute
810 | configTableAttribute = subcategoryName
811 | subcategoryName = nil
812 | end
813 |
814 | if type(defaultValue) ~= "boolean" then
815 | color = info
816 | info = displayValueProxies
817 | displayValueProxies = displayText
818 | displayText = defaultValue
819 | defaultValue = false
820 | end
821 |
822 | if type(displayValueProxies) ~= "table" or type(info) == "userdata" or type(info) == "nil" then
823 | color = info
824 | info = displayValueProxies
825 | displayValueProxies = nil
826 | end
827 |
828 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.BOOLEAN, categoryName, subcategoryName,
829 | configTableAttribute, nil, nil, nil, defaultValue, displayText, displayValueProxies, nil, info, color,
830 | "AddBooleanSetting")
831 | end
832 |
833 | function ModConfigMenu.AddNumberSetting(categoryName, subcategoryName, configTableAttribute, minValue, maxValue, modifyBy
834 | , defaultValue, displayText, displayValueProxies, info, color)
835 | --move args around
836 | if type(configTableAttribute) ~= "string" then
837 | color = info
838 | info = displayValueProxies
839 | displayValueProxies = displayText
840 | displayText = defaultValue
841 | defaultValue = modifyBy
842 | modifyBy = maxValue
843 | maxValue = minValue
844 | minValue = configTableAttribute
845 | configTableAttribute = subcategoryName
846 | subcategoryName = nil
847 | end
848 |
849 | if type(defaultValue) == "string" then
850 | color = info
851 | info = displayValueProxies
852 | displayValueProxies = displayText
853 | displayText = defaultValue
854 | defaultValue = modifyBy
855 | modifyBy = nil
856 | end
857 |
858 | if type(displayValueProxies) ~= "table" or type(info) == "userdata" or type(info) == "nil" then
859 | color = info
860 | info = displayValueProxies
861 | displayValueProxies = nil
862 | end
863 |
864 | --set default values
865 | defaultValue = defaultValue or 0
866 | minValue = minValue or 0
867 | maxValue = maxValue or 10
868 | modifyBy = modifyBy or 1
869 |
870 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.NUMBER, categoryName, subcategoryName,
871 | configTableAttribute, minValue, maxValue, modifyBy, defaultValue, displayText, displayValueProxies, nil, info,
872 | color
873 | ,
874 | "AddNumberSetting")
875 | end
876 |
877 | function ModConfigMenu.AddScrollSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText,
878 | info, color)
879 | --move args around
880 | if type(configTableAttribute) ~= "string" then
881 | color = info
882 | info = displayText
883 | displayText = defaultValue
884 | defaultValue = configTableAttribute
885 | configTableAttribute = subcategoryName
886 | subcategoryName = nil
887 | end
888 |
889 | if type(defaultValue) ~= "number" then
890 | color = info
891 | info = displayText
892 | displayText = defaultValue
893 | defaultValue = nil
894 | end
895 |
896 | --set default values
897 | defaultValue = defaultValue or 0
898 |
899 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.SCROLL, categoryName, subcategoryName,
900 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, nil, info, color, "AddScrollSetting")
901 | end
902 |
903 | function ModConfigMenu.AddKeyboardSetting(categoryName, subcategoryName, configTableAttribute, defaultValue, displayText
904 | , displayDevice, info, color)
905 | --move args around
906 | if type(configTableAttribute) ~= "string" then
907 | color = info
908 | info = displayDevice
909 | displayDevice = displayText
910 | displayText = defaultValue
911 | defaultValue = configTableAttribute
912 | configTableAttribute = subcategoryName
913 | subcategoryName = nil
914 | end
915 |
916 | if type(defaultValue) ~= "number" then
917 | color = info
918 | info = displayText
919 | displayText = defaultValue
920 | defaultValue = nil
921 | end
922 |
923 | if type(displayDevice) ~= "boolean" then
924 | color = info
925 | info = displayDevice
926 | displayDevice = false
927 | end
928 |
929 | --set default values
930 | defaultValue = defaultValue or -1
931 |
932 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.KEYBIND_KEYBOARD, categoryName, subcategoryName,
933 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, displayDevice, info, color,
934 | "AddKeyboardSetting")
935 | end
936 |
937 | function ModConfigMenu.AddControllerSetting(categoryName, subcategoryName, configTableAttribute, defaultValue,
938 | displayText, displayDevice, info, color)
939 | --move args around
940 | if type(configTableAttribute) ~= "string" then
941 | color = info
942 | info = displayDevice
943 | displayDevice = displayText
944 | displayText = defaultValue
945 | defaultValue = configTableAttribute
946 | configTableAttribute = subcategoryName
947 | subcategoryName = nil
948 | end
949 |
950 | if type(defaultValue) ~= "number" then
951 | color = info
952 | info = displayText
953 | displayText = defaultValue
954 | defaultValue = nil
955 | end
956 |
957 | if type(displayDevice) ~= "boolean" then
958 | color = info
959 | info = displayDevice
960 | displayDevice = false
961 | end
962 |
963 | --set default values
964 | defaultValue = defaultValue or -1
965 |
966 | return ModConfigMenu.SimpleAddSetting(ModConfigMenu.OptionType.KEYBIND_CONTROLLER, categoryName, subcategoryName,
967 | configTableAttribute, nil, nil, nil, defaultValue, displayText, nil, displayDevice, info, color,
968 | "AddControllerSetting")
969 | end
970 |
971 | function ModConfigMenu.RemoveSetting(categoryName, subcategoryName, settingAttribute)
972 | if settingAttribute == nil then
973 | settingAttribute = subcategoryName
974 | subcategoryName = nil
975 | end
976 |
977 | if type(categoryName) ~= "string" and type(categoryName) ~= "number" then
978 | error("ModConfigMenu.RemoveSetting - No valid category name provided", 2)
979 | end
980 |
981 | subcategoryName = subcategoryName or "Uncategorized"
982 | if type(subcategoryName) ~= "string" and type(subcategoryName) ~= "number" then
983 | error("ModConfigMenu.RemoveSetting - No valid subcategory name provided", 2)
984 | end
985 |
986 | local categoryID = ModConfigMenu.GetCategoryIDByName(categoryName)
987 | if categoryID then
988 | local subcategoryID = ModConfigMenu.GetSubcategoryIDByName(categoryID, subcategoryName)
989 | if subcategoryID then
990 | --loop to find matching attribute
991 | for i = #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, 1, -1 do
992 | if ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i]
993 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Attribute
994 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Attribute == settingAttribute then
995 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, i)
996 | return true
997 | end
998 | end
999 |
1000 | --loop to find matching display
1001 | for i = #ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, 1, -1 do
1002 | if ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i]
1003 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Display
1004 | and ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options[i].Display == settingAttribute then
1005 | table.remove(ModConfigMenu.MenuData[categoryID].Subcategories[subcategoryID].Options, i)
1006 | return true
1007 | end
1008 | end
1009 | end
1010 | end
1011 |
1012 | return false
1013 | end
1014 |
1015 | --------------------------
1016 | --GENERAL SETTINGS SETUP--
1017 | --------------------------
1018 | ModConfigMenu.SetCategoryInfo("General", "Settings that affect the majority of mods")
1019 |
1020 | ----------------------
1021 | --HUD OFFSET SETTING--
1022 | ----------------------
1023 | local hudOffsetSetting = ModConfigMenu.AddScrollSetting(
1024 | "General", --category
1025 | "HudOffset", --attribute in table
1026 | Options and Options.HUDOffset * 10 or 0, --default value
1027 | "Hud Offset", --display text
1028 | "How far from the corners of the screen custom hud elements will be.$newlineTry to make this match your base-game setting."
1029 | )
1030 |
1031 | hudOffsetSetting.HideControls = true -- hide controls so the screen corner graphics are easier to see
1032 | hudOffsetSetting.ShowOffset = true -- shows screen offset
1033 | vanillaMCMOptionRegisterSave(hudOffsetSetting)
1034 |
1035 | --set up callback
1036 | local oldHudOffsetOnChange = hudOffsetSetting.OnChange
1037 | hudOffsetSetting.OnChange = function(currentValue)
1038 | --update screenhelper's offset
1039 | if ScreenHelper then
1040 | ScreenHelper.SetOffset(currentValue)
1041 | end
1042 |
1043 | return oldHudOffsetOnChange(currentValue)
1044 | end
1045 |
1046 | --------------------
1047 | --OVERLAYS SETTING--
1048 | --------------------
1049 | local overlays = ModConfigMenu.AddBooleanSetting(
1050 | "General", --category
1051 | "Overlays", --attribute in table
1052 | true, --default value
1053 | "Overlays", --display text
1054 | {
1055 | --value display text
1056 | [true] = "On",
1057 | [false] = "Off"
1058 | },
1059 | "Enable or disable custom visual overlays, like screen-wide fog."
1060 | )
1061 | vanillaMCMOptionRegisterSave(overlays)
1062 |
1063 | -----------------------
1064 | --CHARGE BARS SETTING--
1065 | -----------------------
1066 | local chargeBars = ModConfigMenu.AddBooleanSetting(
1067 | "General", --category
1068 | "ChargeBars", --attribute in table
1069 | false, --default value
1070 | "Charge Bars", --display text
1071 | {
1072 | --value display text
1073 | [true] = "On",
1074 | [false] = "Off"
1075 | },
1076 | "Enable or disable custom charge bar visuals for mod effects, like those from chargeable items."
1077 | )
1078 | vanillaMCMOptionRegisterSave(chargeBars)
1079 |
1080 | ---------------------
1081 | --BIG BOOKS SETTING--
1082 | ---------------------
1083 | local bigBooks = ModConfigMenu.AddBooleanSetting(
1084 | "General", --category
1085 | "BigBooks", --attribute in table
1086 | true, --default value
1087 | "Bigbooks", --display text
1088 | {
1089 | --value display text
1090 | [true] = "On",
1091 | [false] = "Off"
1092 | },
1093 | "Enable or disable custom big-book overlays which can appear when an active item is used."
1094 | )
1095 | vanillaMCMOptionRegisterSave(bigBooks)
1096 |
1097 | ---------------------
1098 | --ANNOUNCER SETTING--
1099 | ---------------------
1100 |
1101 | local announcer = ModConfigMenu.AddNumberSetting(
1102 | "General", --category
1103 | "Announcer", --attribute in table
1104 | 0, --minimum value
1105 | 2, --max value
1106 | 0, --default value,
1107 | "Announcer", --display text
1108 | {
1109 | --value display text
1110 | [0] = "Sometimes",
1111 | [1] = "Never",
1112 | [2] = "Always"
1113 | },
1114 | "Choose how often a voice-over will play when a pocket item (pill or card) is used."
1115 | )
1116 | vanillaMCMOptionRegisterSave(announcer)
1117 |
1118 | --------------------------
1119 | --GENERAL SETTINGS CLOSE--
1120 | --------------------------
1121 |
1122 | ModConfigMenu.AddSpace("General") --SPACE
1123 |
1124 | ModConfigMenu.AddText("General", "These settings apply to")
1125 | ModConfigMenu.AddText("General", "all mods which support them")
1126 |
1127 | ----------------------------------
1128 | --MOD CONFIG MENU SETTINGS SETUP--
1129 | ----------------------------------
1130 |
1131 | ModConfigMenu.SetCategoryInfo("Mod Config Menu",
1132 | "Settings specific to Mod Config Menu.$newlineChange keybindings for the menu here.")
1133 |
1134 | ModConfigMenu.AddTitle("Mod Config Menu", "Version " .. tostring(ModConfigMenu.Version) .. " !") --VERSION INDICATOR
1135 |
1136 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1137 |
1138 | ----------------------
1139 | --OPEN MENU KEYBOARD--
1140 | ----------------------
1141 |
1142 | local openMenuKeyboardSetting = ModConfigMenu.AddKeyboardSetting(
1143 | "Mod Config Menu", --category
1144 | "OpenMenuKeyboard", --attribute in table
1145 | Keyboard.KEY_L, --default value
1146 | "Open Menu", --display text
1147 | true, --if (keyboard) is displayed after the key text
1148 | "Choose what button on your keyboard will open Mod Config Menu."
1149 | )
1150 |
1151 | openMenuKeyboardSetting.IsOpenMenuKeybind = true
1152 | vanillaMCMOptionRegisterSave(openMenuKeyboardSetting)
1153 |
1154 | ------------------------
1155 | --OPEN MENU CONTROLLER--
1156 | ------------------------
1157 |
1158 | local openMenuControllerSetting = ModConfigMenu.AddControllerSetting(
1159 | "Mod Config Menu", --category
1160 | "OpenMenuController", --attribute in table
1161 | Controller.STICK_RIGHT, --default value
1162 | "Open Menu", --display text
1163 | true, --if (controller) is displayed after the key text
1164 | "Choose what button on your controller will open Mod Config Menu."
1165 | )
1166 | openMenuControllerSetting.IsOpenMenuKeybind = true
1167 | vanillaMCMOptionRegisterSave(openMenuControllerSetting)
1168 |
1169 | --f10 note
1170 | ModConfigMenu.AddText("Mod Config Menu", "F10 will always open this menu.")
1171 |
1172 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1173 |
1174 | ------------
1175 | --HIDE HUD--
1176 | ------------
1177 |
1178 | local hideHudSetting = ModConfigMenu.AddBooleanSetting(
1179 | "Mod Config Menu", --category
1180 | "HideHudInMenu", --attribute in table
1181 | true, --default value
1182 | "Hide HUD", --display text
1183 | {
1184 | --value display text
1185 | [true] = "Yes",
1186 | [false] = "No"
1187 | },
1188 | "Enable or disable the hud when this menu is open."
1189 | )
1190 | vanillaMCMOptionRegisterSave(hideHudSetting)
1191 |
1192 | --actively modify the hud visibility as this setting changes
1193 | local oldHideHudOnChange = hideHudSetting.OnChange
1194 | hideHudSetting.OnChange = function(currentValue)
1195 | oldHideHudOnChange(currentValue)
1196 |
1197 | local game = Game()
1198 | if REPENTANCE then
1199 | local hud = game:GetHUD()
1200 |
1201 | if currentValue then
1202 | if hud:IsVisible() then
1203 | hud:SetVisible(false)
1204 | end
1205 | else
1206 | if not hud:IsVisible() then
1207 | hud:SetVisible(true)
1208 | end
1209 | end
1210 | else
1211 | local seeds = game:GetSeeds()
1212 |
1213 | if currentValue then
1214 | if not seeds:HasSeedEffect(SeedEffect.SEED_NO_HUD) then
1215 | seeds:AddSeedEffect(SeedEffect.SEED_NO_HUD)
1216 | end
1217 | else
1218 | if seeds:HasSeedEffect(SeedEffect.SEED_NO_HUD) then
1219 | seeds:RemoveSeedEffect(SeedEffect.SEED_NO_HUD)
1220 | end
1221 | end
1222 | end
1223 | end
1224 |
1225 | ----------------------------
1226 | --RESET TO DEFAULT KEYBIND--
1227 | ----------------------------
1228 |
1229 | local resetKeybindSetting = ModConfigMenu.AddKeyboardSetting(
1230 | "Mod Config Menu", --category
1231 | "ResetToDefault", --attribute in table
1232 | Keyboard.KEY_F11, --default value
1233 | "Reset To Default Keybind", --display text
1234 | "Press this button on your keyboard to reset a setting to its default value."
1235 | )
1236 | resetKeybindSetting.IsResetKeybind = true
1237 | vanillaMCMOptionRegisterSave(resetKeybindSetting)
1238 |
1239 | -----------------
1240 | --SHOW CONTROLS--
1241 | -----------------
1242 |
1243 | local showControls = ModConfigMenu.AddBooleanSetting(
1244 | "Mod Config Menu", --category
1245 | "ShowControls", --attribute in table
1246 | true, --default value
1247 | "Show Controls", --display text
1248 | {
1249 | --value display text
1250 | [true] = "Yes",
1251 | [false] = "No"
1252 | },
1253 | "Disable this to remove the back and select widgets at the lower corners of the screen and remove the bottom start-up message."
1254 | )
1255 | vanillaMCMOptionRegisterSave(showControls)
1256 |
1257 | ModConfigMenu.AddSpace("Mod Config Menu") --SPACE
1258 |
1259 | -----------------
1260 | --COMPATIBILITY--
1261 | -----------------
1262 |
1263 | local compatibilitySetting = ModConfigMenu.AddBooleanSetting(
1264 | "Mod Config Menu", --category
1265 | "CompatibilityLayer", --attribute in table
1266 | false, --default value
1267 | "Disable Legacy Warnings", --display text
1268 | {
1269 | --value display text
1270 | [true] = "Yes",
1271 | [false] = "No"
1272 | },
1273 | "Use this setting to prevent warnings from being printed to the console for mods that use outdated features of Mod Config Menu."
1274 | )
1275 | vanillaMCMOptionRegisterSave(compatibilitySetting)
1276 |
1277 | local configMenuSubcategoriesCanShow = 3
1278 |
1279 | local configMenuInSubcategory = false
1280 | local configMenuInOptions = false
1281 | local configMenuInPopup = false
1282 |
1283 | local holdingCounterDown = 0
1284 | local holdingCounterUp = 0
1285 | local holdingCounterRight = 0
1286 | local holdingCounterLeft = 0
1287 |
1288 | local configMenuPositionCursorCategory = 1
1289 | local configMenuPositionCursorSubcategory = 1
1290 | local configMenuPositionCursorOption = 1
1291 |
1292 | local configMenuPositionFirstSubcategory = 1
1293 |
1294 | --valid action presses
1295 | local actionsDown = { ButtonAction.ACTION_DOWN, ButtonAction.ACTION_SHOOTDOWN, ButtonAction.ACTION_MENUDOWN }
1296 | local actionsUp = { ButtonAction.ACTION_UP, ButtonAction.ACTION_SHOOTUP, ButtonAction.ACTION_MENUUP }
1297 | local actionsRight = { ButtonAction.ACTION_RIGHT, ButtonAction.ACTION_SHOOTRIGHT, ButtonAction.ACTION_MENURIGHT }
1298 | local actionsLeft = { ButtonAction.ACTION_LEFT, ButtonAction.ACTION_SHOOTLEFT, ButtonAction.ACTION_MENULEFT }
1299 | local actionsBack = { ButtonAction.ACTION_PILLCARD, ButtonAction.ACTION_MAP, ButtonAction.ACTION_MENUBACK }
1300 | local actionsSelect = { ButtonAction.ACTION_ITEM, ButtonAction.ACTION_PAUSE, ButtonAction.ACTION_MENUCONFIRM,
1301 | ButtonAction.ACTION_BOMB }
1302 |
1303 | --ignore these buttons for the above actions
1304 | local ignoreActionButtons = { Controller.BUTTON_A, Controller.BUTTON_B, Controller.BUTTON_X, Controller.BUTTON_Y }
1305 |
1306 | local currentMenuCategory = nil
1307 | local currentMenuSubcategory = nil
1308 | local currentMenuOption = nil
1309 | local function updateCurrentMenuVars()
1310 | if ModConfigMenu.MenuData[configMenuPositionCursorCategory] then
1311 | currentMenuCategory = ModConfigMenu.MenuData[configMenuPositionCursorCategory]
1312 | if currentMenuCategory.Subcategories and currentMenuCategory.Subcategories[configMenuPositionCursorSubcategory] then
1313 | currentMenuSubcategory = currentMenuCategory.Subcategories[configMenuPositionCursorSubcategory]
1314 | if currentMenuSubcategory.Options and currentMenuSubcategory.Options[configMenuPositionCursorOption] then
1315 | currentMenuOption = currentMenuSubcategory.Options[configMenuPositionCursorOption]
1316 | end
1317 | end
1318 | end
1319 | end
1320 |
1321 | --leaving/entering menu sections
1322 | function ModConfigMenu.EnterPopup()
1323 | if configMenuInSubcategory and configMenuInOptions and not configMenuInPopup then
1324 | local foundValidPopup = false
1325 | if currentMenuOption
1326 | and currentMenuOption.Type
1327 | and currentMenuOption.Type ~= ModConfigMenu.OptionType.SPACE
1328 | and (currentMenuOption.Popup or currentMenuOption.Restart or currentMenuOption.Rerun) then
1329 | foundValidPopup = true
1330 | end
1331 | if foundValidPopup then
1332 | local popupSpritesheet = ModConfigMenu.PopupGfx.THIN_SMALL
1333 | if currentMenuOption.PopupGfx and type(currentMenuOption.PopupGfx) == "string" then
1334 | popupSpritesheet = currentMenuOption.PopupGfx
1335 | end
1336 | PopupSprite:ReplaceSpritesheet(5, popupSpritesheet)
1337 | PopupSprite:LoadGraphics()
1338 | configMenuInPopup = true
1339 | end
1340 | end
1341 | end
1342 |
1343 | function ModConfigMenu.EnterOptions()
1344 | if configMenuInSubcategory and not configMenuInOptions then
1345 | if currentMenuSubcategory
1346 | and currentMenuSubcategory.Options
1347 | and #currentMenuSubcategory.Options > 0 then
1348 | for optionIndex = 1, #currentMenuSubcategory.Options do
1349 | local thisOption = currentMenuSubcategory.Options[optionIndex]
1350 |
1351 | if thisOption.Type
1352 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
1353 | and
1354 | (
1355 | not thisOption.NoCursorHere or
1356 | (type(thisOption.NoCursorHere) == "function" and not thisOption.NoCursorHere()))
1357 | and thisOption.Display then
1358 | configMenuPositionCursorOption = optionIndex
1359 | configMenuInOptions = true
1360 | OptionsCursorSpriteUp.Color = colorDefault
1361 | OptionsCursorSpriteDown.Color = colorDefault
1362 |
1363 | break
1364 | end
1365 | end
1366 | end
1367 | end
1368 | end
1369 |
1370 | function ModConfigMenu.EnterSubcategory()
1371 | if not configMenuInSubcategory then
1372 | configMenuInSubcategory = true
1373 | SubcategoryCursorSpriteLeft.Color = colorDefault
1374 | SubcategoryCursorSpriteRight.Color = colorDefault
1375 | SubcategoryDividerSprite.Color = colorDefault
1376 |
1377 | local hasUsableCategories = false
1378 | if currentMenuCategory.Subcategories then
1379 | for j = 1, #currentMenuCategory.Subcategories do
1380 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
1381 | hasUsableCategories = true
1382 | end
1383 | end
1384 | end
1385 |
1386 | if not hasUsableCategories then
1387 | ModConfigMenu.EnterOptions()
1388 | end
1389 | end
1390 | end
1391 |
1392 | function ModConfigMenu.LeavePopup()
1393 | if configMenuInSubcategory and configMenuInOptions and configMenuInPopup then
1394 | if currentMenuOption then
1395 | if currentMenuOption.Restart then
1396 | restartWarnMessage = "One or more settings require you to restart the game"
1397 | elseif currentMenuOption.Rerun then
1398 | rerunWarnMessage = "One or more settings require you to start a new run"
1399 | end
1400 | end
1401 |
1402 | configMenuInPopup = false
1403 | end
1404 | end
1405 |
1406 | function ModConfigMenu.LeaveOptions()
1407 | if configMenuInSubcategory and configMenuInOptions then
1408 | configMenuInOptions = false
1409 | OptionsCursorSpriteUp.Color = colorHalf
1410 | OptionsCursorSpriteDown.Color = colorHalf
1411 |
1412 | local hasUsableCategories = false
1413 | if currentMenuCategory.Subcategories then
1414 | for j = 1, #currentMenuCategory.Subcategories do
1415 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
1416 | hasUsableCategories = true
1417 | end
1418 | end
1419 | end
1420 |
1421 | if not hasUsableCategories then
1422 | ModConfigMenu.LeaveSubcategory()
1423 | end
1424 | end
1425 | end
1426 |
1427 | function ModConfigMenu.LeaveSubcategory()
1428 | if configMenuInSubcategory then
1429 | configMenuInSubcategory = false
1430 | SubcategoryCursorSpriteLeft.Color = colorHalf
1431 | SubcategoryCursorSpriteRight.Color = colorHalf
1432 | SubcategoryDividerSprite.Color = colorHalf
1433 | end
1434 | end
1435 |
1436 | local mainSpriteColor = colorDefault
1437 | local optionsSpriteColor = colorDefault
1438 | local optionsSpriteColorAlpha = colorHalf
1439 | local mainFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1440 | local leftFontColor = KColor(35 / 255, 31 / 255, 30 / 255, 1)
1441 | local leftFontColorSelected = KColor(35 / 255, 50 / 255, 70 / 255, 1)
1442 |
1443 | local optionsFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1444 | local optionsFontColorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.5)
1445 | local optionsFontColorNoCursor = KColor(34 / 255, 32 / 255, 30 / 255, 0.8)
1446 | local optionsFontColorNoCursorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.4)
1447 | local optionsFontColorTitle = KColor(50 / 255, 0, 0, 1)
1448 | local optionsFontColorTitleAlpha = KColor(50 / 255, 0, 0, 0.5)
1449 |
1450 | local subcategoryFontColor = KColor(34 / 255, 32 / 255, 30 / 255, 1)
1451 | local subcategoryFontColorSelected = KColor(34 / 255, 50 / 255, 70 / 255, 1)
1452 | local subcategoryFontColorAlpha = KColor(34 / 255, 32 / 255, 30 / 255, 0.5)
1453 | local subcategoryFontColorSelectedAlpha = KColor(34 / 255, 50 / 255, 70 / 255, 0.5)
1454 |
1455 | function ModConfigMenu.ConvertDisplayToTextTable(displayValue, lineWidth, font)
1456 | lineWidth = lineWidth or 340
1457 |
1458 | local textTableDisplay = {}
1459 | if type(displayValue) == "function" then
1460 | displayValue = displayValue()
1461 | end
1462 |
1463 | if type(displayValue) == "string" then
1464 | textTableDisplay = { displayValue }
1465 | elseif type(displayValue) == "table" then
1466 | textTableDisplay = SaveHelper.CopyTable(displayValue)
1467 | else
1468 | textTableDisplay = { tostring(displayValue) }
1469 | end
1470 |
1471 | if type(textTableDisplay) == "string" then
1472 | textTableDisplay = { textTableDisplay }
1473 | end
1474 |
1475 | --create new lines based on $newline modifier
1476 | local textTableDisplayAfterNewlines = {}
1477 | for lineIndex = 1, #textTableDisplay do
1478 | local line = textTableDisplay[lineIndex]
1479 | local startIdx, endIdx = string.find(line, "$newline")
1480 | while startIdx do
1481 | local newline = string.sub(line, 0, startIdx - 1)
1482 | table.insert(textTableDisplayAfterNewlines, newline)
1483 |
1484 | line = string.sub(line, endIdx + 1)
1485 |
1486 | startIdx, endIdx = string.find(line, "$newline")
1487 | end
1488 | table.insert(textTableDisplayAfterNewlines, line)
1489 | end
1490 |
1491 | --dynamic string new line creation, based on code by Wofsauge
1492 | local textTableDisplayAfterWordLength = {}
1493 | for lineIndex = 1, #textTableDisplayAfterNewlines do
1494 | local line = textTableDisplayAfterNewlines[lineIndex]
1495 | local curLength = 0
1496 | local text = ""
1497 | for word in string.gmatch(tostring(line), "([^%s]+)") do
1498 | local wordLength = font:GetStringWidthUTF8(word)
1499 |
1500 | if curLength + wordLength <= lineWidth or curLength < 12 then
1501 | text = text .. word .. " "
1502 | curLength = curLength + wordLength
1503 | else
1504 | table.insert(textTableDisplayAfterWordLength, text)
1505 | text = word .. " "
1506 | curLength = wordLength
1507 | end
1508 | end
1509 | table.insert(textTableDisplayAfterWordLength, text)
1510 | end
1511 |
1512 | return textTableDisplayAfterWordLength
1513 | end
1514 |
1515 | --set up screen corner display for hud offset
1516 | local HudOffsetVisualTopLeft = ModConfigMenu.GetMenuAnm2Sprite("Offset", 0)
1517 | local HudOffsetVisualTopRight = ModConfigMenu.GetMenuAnm2Sprite("Offset", 1)
1518 | local HudOffsetVisualBottomRight = ModConfigMenu.GetMenuAnm2Sprite("Offset", 2)
1519 | local HudOffsetVisualBottomLeft = ModConfigMenu.GetMenuAnm2Sprite("Offset", 3)
1520 |
1521 | --render the menu
1522 | local leftCurrentOffset = 0
1523 | local optionsCurrentOffset = 0
1524 | ModConfigMenu.ControlsEnabled = true
1525 | function ModConfigMenu.PostRender()
1526 | local game = Game()
1527 | local isPaused = game:IsPaused() or AwaitingTextInput
1528 |
1529 | local sfx = SFXManager()
1530 |
1531 | local pressingButton = ""
1532 |
1533 | local pressingNonRebindableKey = false
1534 | local pressedToggleMenu = false
1535 |
1536 | local openMenuGlobal = Keyboard.KEY_F10
1537 | local openMenuKeyboard = ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard
1538 | local openMenuController = ModConfigMenu.Config["Mod Config Menu"].OpenMenuController
1539 |
1540 | local takeScreenshot = Keyboard.KEY_F12
1541 |
1542 | --handle version display on game start
1543 | if versionPrintTimer > 0 then
1544 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
1545 |
1546 | local openMenuButton = Keyboard.KEY_F10
1547 | if type(ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard) == "number" and
1548 | ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard > -1 then
1549 | openMenuButton = ModConfigMenu.Config["Mod Config Menu"].OpenMenuKeyboard
1550 | end
1551 |
1552 | local openMenuButtonString = "Unknown Key"
1553 | if InputHelper.KeyboardToString[openMenuButton] then
1554 | openMenuButtonString = InputHelper.KeyboardToString[openMenuButton]
1555 | end
1556 |
1557 | local text = "Press " .. openMenuButtonString .. " to open Mod Config Menu"
1558 | local versionPrintColor = KColor(1, 1, 0, (math.min(versionPrintTimer, 60) / 60) * 0.5)
1559 | versionPrintFont:DrawStringUTF8(text, 0, bottomRight.Y - 28, versionPrintColor, math.floor(bottomRight.X), true)
1560 | end
1561 |
1562 | --on-screen warnings
1563 | if restartWarnMessage or rerunWarnMessage then
1564 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
1565 |
1566 | local text = restartWarnMessage or rerunWarnMessage
1567 | local warningPrintColor = KColor(1, 0, 0, 1)
1568 | versionPrintFont:DrawStringUTF8(text, 0, bottomRight.Y - 28, warningPrintColor, math.floor(bottomRight.X), true)
1569 | end
1570 |
1571 | --handle toggling the menu
1572 | if ModConfigMenu.ControlsEnabled and not isPaused then
1573 | for i = 0, 4 do
1574 | if InputHelper.KeyboardTriggered(openMenuGlobal, i)
1575 | or (openMenuKeyboard > -1 and InputHelper.KeyboardTriggered(openMenuKeyboard, i))
1576 | or (openMenuController > -1 and Input.IsButtonTriggered(openMenuController, i)) then
1577 | pressingNonRebindableKey = true
1578 | pressedToggleMenu = true
1579 | if not configMenuInPopup then
1580 | ModConfigMenu.ToggleConfigMenu()
1581 | end
1582 | end
1583 |
1584 | if InputHelper.KeyboardTriggered(takeScreenshot, i) then
1585 | pressingNonRebindableKey = true
1586 | end
1587 | end
1588 | end
1589 |
1590 | --force close the menu in some situations
1591 | if ModConfigMenu.IsVisible then
1592 | if isPaused then
1593 | ModConfigMenu.CloseConfigMenu()
1594 | end
1595 |
1596 | if not ModConfigMenu.RoomIsSafe() then
1597 | ModConfigMenu.CloseConfigMenu()
1598 |
1599 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
1600 | end
1601 | end
1602 |
1603 | --replace dead sea scrolls' controller setting to not conflict with mcm's
1604 | if DeadSeaScrollsMenu and DeadSeaScrollsMenu.GetGamepadToggleSetting then
1605 | local dssControllerToggle = DeadSeaScrollsMenu.GetGamepadToggleSetting()
1606 |
1607 | if DeadSeaScrollsMenu.SaveGamepadToggleSetting then
1608 | if openMenuController == Controller.STICK_RIGHT and
1609 | (dssControllerToggle == 1 or dssControllerToggle == 3 or dssControllerToggle == 4) then
1610 | DeadSeaScrollsMenu.SaveGamepadToggleSetting(2) --force revelations' menu to only use the left stick
1611 | elseif openMenuController == Controller.STICK_LEFT and
1612 | (dssControllerToggle == 1 or dssControllerToggle == 2 or dssControllerToggle == 4) then
1613 | DeadSeaScrollsMenu.SaveGamepadToggleSetting(3) --force revelations' menu to only use the right stick
1614 | end
1615 | end
1616 | end
1617 |
1618 | if ModConfigMenu.IsVisible then
1619 | if ModConfigMenu.ControlsEnabled and not isPaused then
1620 | for i = 0, game:GetNumPlayers() - 1 do
1621 | local player = Isaac.GetPlayer(i)
1622 | local data = player:GetData()
1623 |
1624 | --freeze players and disable their controls
1625 | player.Velocity = vecZero
1626 |
1627 | if not data.ConfigMenuPlayerPosition then
1628 | data.ConfigMenuPlayerPosition = player.Position
1629 | end
1630 | player.Position = data.ConfigMenuPlayerPosition
1631 | if not data.ConfigMenuPlayerControlsDisabled then
1632 | player.ControlsEnabled = false
1633 | data.ConfigMenuPlayerControlsDisabled = true
1634 | end
1635 |
1636 | --disable toggling revelations menu
1637 | if data.input and data.input.menu and data.input.menu.toggle then
1638 | data.input.menu.toggle = false
1639 | end
1640 | end
1641 |
1642 | if not InputHelper.MultipleButtonPressed(ignoreActionButtons) then
1643 | --pressing buttons
1644 | local downButtonPressed = InputHelper.MultipleActionTriggered(actionsDown)
1645 | if downButtonPressed then
1646 | pressingButton = "DOWN"
1647 | end
1648 | local upButtonPressed = InputHelper.MultipleActionTriggered(actionsUp)
1649 | if upButtonPressed then
1650 | pressingButton = "UP"
1651 | end
1652 | local rightButtonPressed = InputHelper.MultipleActionTriggered(actionsRight)
1653 | if rightButtonPressed then
1654 | pressingButton = "RIGHT"
1655 | end
1656 | local leftButtonPressed = InputHelper.MultipleActionTriggered(actionsLeft)
1657 | if leftButtonPressed then
1658 | pressingButton = "LEFT"
1659 | end
1660 | local backButtonPressed = InputHelper.MultipleActionTriggered(actionsBack) or
1661 | InputHelper.MultipleKeyboardTriggered({ Keyboard.KEY_BACKSPACE })
1662 | if backButtonPressed then
1663 | pressingButton = "BACK"
1664 | local possiblyPressedButton = InputHelper.MultipleKeyboardTriggered(Keyboard)
1665 | if possiblyPressedButton then
1666 | ModConfigMenu.Config.LastBackPressed = possiblyPressedButton
1667 | end
1668 | end
1669 | local selectButtonPressed = InputHelper.MultipleActionTriggered(actionsSelect)
1670 | if selectButtonPressed then
1671 | pressingButton = "SELECT"
1672 | local possiblyPressedButton = InputHelper.MultipleKeyboardTriggered(Keyboard)
1673 | if possiblyPressedButton then
1674 | ModConfigMenu.Config.LastSelectPressed = possiblyPressedButton
1675 | end
1676 | end
1677 | if ModConfigMenu.Config["Mod Config Menu"].ResetToDefault > -1 and
1678 | InputHelper.MultipleKeyboardTriggered({ ModConfigMenu.Config["Mod Config Menu"].ResetToDefault }) then
1679 | pressingButton = "RESET"
1680 | end
1681 |
1682 | --holding buttons
1683 | if InputHelper.MultipleActionPressed(actionsDown) then
1684 | holdingCounterDown = holdingCounterDown + 1
1685 | else
1686 | holdingCounterDown = 0
1687 | end
1688 | if holdingCounterDown > 20 and holdingCounterDown % 5 == 0 then
1689 | pressingButton = "DOWN"
1690 | end
1691 | if InputHelper.MultipleActionPressed(actionsUp) then
1692 | holdingCounterUp = holdingCounterUp + 1
1693 | else
1694 | holdingCounterUp = 0
1695 | end
1696 | if holdingCounterUp > 20 and holdingCounterUp % 5 == 0 then
1697 | pressingButton = "UP"
1698 | end
1699 | if InputHelper.MultipleActionPressed(actionsRight) then
1700 | holdingCounterRight = holdingCounterRight + 1
1701 | else
1702 | holdingCounterRight = 0
1703 | end
1704 | if holdingCounterRight > 20 and holdingCounterRight % 5 == 0 then
1705 | pressingButton = "RIGHT"
1706 | end
1707 | if InputHelper.MultipleActionPressed(actionsLeft) then
1708 | holdingCounterLeft = holdingCounterLeft + 1
1709 | else
1710 | holdingCounterLeft = 0
1711 | end
1712 | if holdingCounterLeft > 20 and holdingCounterLeft % 5 == 0 then
1713 | pressingButton = "LEFT"
1714 | end
1715 | else
1716 | if InputHelper.MultipleButtonTriggered({ Controller.BUTTON_B }) then
1717 | pressingButton = "BACK"
1718 | pressingNonRebindableKey = true
1719 | end
1720 | if InputHelper.MultipleButtonTriggered({ Controller.BUTTON_A }) then
1721 | pressingButton = "SELECT"
1722 | pressingNonRebindableKey = true
1723 | end
1724 | end
1725 |
1726 | if pressingButton ~= "" then
1727 | pressingNonRebindableKey = true
1728 | end
1729 | end
1730 |
1731 | updateCurrentMenuVars()
1732 |
1733 | local lastCursorCategoryPosition = configMenuPositionCursorCategory
1734 | local lastCursorSubcategoryPosition = configMenuPositionCursorSubcategory
1735 | local lastCursorOptionsPosition = configMenuPositionCursorOption
1736 |
1737 | local enterPopup = false
1738 | local leavePopup = false
1739 |
1740 | local optionChanged = false
1741 |
1742 | local enterOptions = false
1743 | local leaveOptions = false
1744 |
1745 | local enterSubcategory = false
1746 | local leaveSubcategory = false
1747 |
1748 | if configMenuInPopup then
1749 | if currentMenuOption then
1750 | local optionType = currentMenuOption.Type
1751 | local optionCurrent = currentMenuOption.CurrentSetting
1752 | local optionOnChange = currentMenuOption.OnChange
1753 |
1754 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD
1755 | or optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER
1756 | or currentMenuOption.OnSelect then
1757 | if not isPaused then
1758 | if pressingNonRebindableKey
1759 | and not (pressingButton == "BACK"
1760 | or pressingButton == "LEFT"
1761 | or (currentMenuOption.OnSelect and (pressingButton == "SELECT" or pressingButton == "RIGHT"))
1762 | or (currentMenuOption.IsResetKeybind and pressingButton == "RESET")
1763 | or (currentMenuOption.IsOpenMenuKeybind and pressedToggleMenu)) then
1764 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
1765 | else
1766 | local numberToChange = nil
1767 | local receivedInput = false
1768 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
1769 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
1770 | numberToChange = optionCurrent
1771 |
1772 | if type(optionCurrent) == "function" then
1773 | numberToChange = optionCurrent()
1774 | end
1775 |
1776 | if pressingButton == "BACK" or pressingButton == "LEFT" then
1777 | numberToChange = nil
1778 | receivedInput = true
1779 | else
1780 | for i = 0, 4 do
1781 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD then
1782 | for j = 32, 400 do
1783 | if InputHelper.KeyboardTriggered(j, i) then
1784 | numberToChange = j
1785 | receivedInput = true
1786 | break
1787 | end
1788 | end
1789 | else
1790 | for j = 0, 31 do
1791 | if Input.IsButtonTriggered(j, i) then
1792 | numberToChange = j
1793 | receivedInput = true
1794 | break
1795 | end
1796 | end
1797 | end
1798 | end
1799 | end
1800 | elseif currentMenuOption.OnSelect then
1801 | if pressingButton == "BACK" or pressingButton == "LEFT" then
1802 | receivedInput = true
1803 | end
1804 | if pressingButton == "SELECT" or pressingButton == "RIGHT" then
1805 | numberToChange = true
1806 | receivedInput = true
1807 | end
1808 | end
1809 |
1810 | if receivedInput then
1811 | if optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
1812 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER then
1813 | if type(optionCurrent) == "function" then
1814 | if optionOnChange then
1815 | optionOnChange(numberToChange)
1816 | end
1817 | elseif type(optionCurrent) == "number" then
1818 | currentMenuOption.CurrentSetting = numberToChange
1819 | end
1820 |
1821 | --callback
1822 | --[[
1823 | CustomCallbackHelper.CallCallbacks
1824 | (
1825 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
1826 | nil,
1827 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
1828 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
1829 | )
1830 | --]]
1831 | elseif currentMenuOption.OnSelect and numberToChange then
1832 | currentMenuOption.OnSelect()
1833 | end
1834 |
1835 | leavePopup = true
1836 |
1837 | local sound = currentMenuOption.Sound
1838 | if not sound then
1839 | sound = SoundEffect.SOUND_PLOP
1840 | end
1841 | if sound >= 0 then
1842 | sfx:Play(sound, 1, 0, false, 1)
1843 | end
1844 | end
1845 | end
1846 | end
1847 | end
1848 | end
1849 |
1850 | if currentMenuOption.Restart or currentMenuOption.Rerun then
1851 | --confirmed left press
1852 | if pressingButton == "RIGHT" then
1853 | leavePopup = true
1854 | end
1855 |
1856 | --confirmed back press
1857 | if pressingButton == "SELECT" then
1858 | leavePopup = true
1859 | end
1860 | end
1861 |
1862 | --confirmed left press
1863 | if pressingButton == "LEFT" then
1864 | leavePopup = true
1865 | end
1866 |
1867 | --confirmed back press
1868 | if pressingButton == "BACK" then
1869 | leavePopup = true
1870 | end
1871 | elseif configMenuInOptions then
1872 | --confirmed down press
1873 | if pressingButton == "DOWN" then
1874 | configMenuPositionCursorOption = configMenuPositionCursorOption + 1 --move options cursor down
1875 | end
1876 |
1877 | --confirmed up press
1878 | if pressingButton == "UP" then
1879 | configMenuPositionCursorOption = configMenuPositionCursorOption - 1 --move options cursor up
1880 | end
1881 |
1882 | if pressingButton == "SELECT" or pressingButton == "RIGHT" or pressingButton == "LEFT" or
1883 | (pressingButton == "RESET" and currentMenuOption and currentMenuOption.Default ~= nil) then
1884 | if pressingButton == "LEFT" then
1885 | leaveOptions = true
1886 | end
1887 |
1888 | if currentMenuOption then
1889 | local optionType = currentMenuOption.Type
1890 | local optionCurrent = currentMenuOption.CurrentSetting
1891 | local optionOnChange = currentMenuOption.OnChange
1892 |
1893 | if optionType == ModConfigMenu.OptionType.SCROLL or optionType == ModConfigMenu.OptionType.NUMBER then
1894 | leaveOptions = false
1895 |
1896 | local numberToChange = optionCurrent
1897 |
1898 | if type(optionCurrent) == "function" then
1899 | numberToChange = optionCurrent()
1900 | end
1901 |
1902 | local modifyBy = currentMenuOption.ModifyBy or 1
1903 | modifyBy = math.max(modifyBy, 0.001)
1904 | if math.floor(modifyBy) == modifyBy then --force modify by into being an integer instead of a float if it should be
1905 | modifyBy = math.floor(modifyBy)
1906 | end
1907 |
1908 | if pressingButton == "RIGHT" or pressingButton == "SELECT" then
1909 | numberToChange = numberToChange + modifyBy
1910 | elseif pressingButton == "LEFT" then
1911 | numberToChange = numberToChange - modifyBy
1912 | elseif pressingButton == "RESET" and currentMenuOption.Default ~= nil then
1913 | numberToChange = currentMenuOption.Default
1914 | if type(currentMenuOption.Default) == "function" then
1915 | numberToChange = currentMenuOption.Default()
1916 | end
1917 | end
1918 |
1919 | if optionType == ModConfigMenu.OptionType.SCROLL then
1920 | numberToChange = math.max(math.min(math.floor(numberToChange), 10), 0)
1921 | else
1922 | if currentMenuOption.Maximum and numberToChange > currentMenuOption.Maximum then
1923 | if not currentMenuOption.NoLoopFromMaxMin and currentMenuOption.Minimum then
1924 | numberToChange = currentMenuOption.Minimum
1925 | else
1926 | numberToChange = currentMenuOption.Maximum
1927 | end
1928 | end
1929 | if currentMenuOption.Minimum and numberToChange < currentMenuOption.Minimum then
1930 | if not currentMenuOption.NoLoopFromMaxMin and currentMenuOption.Maximum then
1931 | numberToChange = currentMenuOption.Maximum
1932 | else
1933 | numberToChange = currentMenuOption.Minimum
1934 | end
1935 | end
1936 | end
1937 |
1938 | if math.floor(modifyBy) ~= modifyBy then --check if modify by is a float
1939 | numberToChange = math.floor((numberToChange * 1000) + 0.5) * 0.001
1940 | else
1941 | numberToChange = math.floor(numberToChange)
1942 | end
1943 |
1944 | if type(optionCurrent) == "function" then
1945 | if optionOnChange then
1946 | optionOnChange(numberToChange)
1947 | end
1948 | optionChanged = true
1949 | elseif type(optionCurrent) == "number" then
1950 | currentMenuOption.CurrentSetting = numberToChange
1951 | optionChanged = true
1952 | end
1953 |
1954 | --callback
1955 | --[[
1956 | CustomCallbackHelper.CallCallbacks
1957 | (
1958 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
1959 | nil,
1960 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
1961 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
1962 | )
1963 | --]]
1964 | local sound = currentMenuOption.Sound
1965 | if not sound then
1966 | sound = SoundEffect.SOUND_PLOP
1967 | end
1968 | if sound >= 0 then
1969 | sfx:Play(sound, 1, 0, false, 1)
1970 | end
1971 | elseif optionType == ModConfigMenu.OptionType.BOOLEAN then
1972 | leaveOptions = false
1973 |
1974 | local boolToChange = optionCurrent
1975 |
1976 | if type(optionCurrent) == "function" then
1977 | boolToChange = optionCurrent()
1978 | end
1979 |
1980 | if pressingButton == "RESET" and currentMenuOption.Default ~= nil then
1981 | boolToChange = currentMenuOption.Default
1982 | if type(currentMenuOption.Default) == "function" then
1983 | boolToChange = currentMenuOption.Default()
1984 | end
1985 | else
1986 | boolToChange = (not boolToChange)
1987 | end
1988 |
1989 | if type(optionCurrent) == "function" then
1990 | if optionOnChange then
1991 | optionOnChange(boolToChange)
1992 | end
1993 | optionChanged = true
1994 | elseif type(optionCurrent) == "boolean" then
1995 | currentMenuOption.CurrentSetting = boolToChange
1996 | optionChanged = true
1997 | end
1998 |
1999 | --callback
2000 | --[[
2001 | CustomCallbackHelper.CallCallbacks
2002 | (
2003 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
2004 | nil,
2005 | {currentMenuOption.CurrentSetting, boolToChange}, --args to send
2006 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
2007 | )
2008 | --]]
2009 | local sound = currentMenuOption.Sound
2010 | if not sound then
2011 | sound = SoundEffect.SOUND_PLOP
2012 | end
2013 | if sound >= 0 then
2014 | sfx:Play(sound, 1, 0, false, 1)
2015 | end
2016 | elseif (
2017 | optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD or
2018 | optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER) and pressingButton == "RESET" and
2019 | currentMenuOption.Default ~= nil then
2020 | local numberToChange = optionCurrent
2021 |
2022 | if type(optionCurrent) == "function" then
2023 | numberToChange = optionCurrent()
2024 | end
2025 |
2026 | numberToChange = currentMenuOption.Default
2027 | if type(currentMenuOption.Default) == "function" then
2028 | numberToChange = currentMenuOption.Default()
2029 | end
2030 |
2031 | if type(optionCurrent) == "function" then
2032 | if optionOnChange then
2033 | optionOnChange(numberToChange)
2034 | end
2035 | optionChanged = true
2036 | elseif type(optionCurrent) == "number" then
2037 | currentMenuOption.CurrentSetting = numberToChange
2038 | optionChanged = true
2039 | end
2040 |
2041 | --callback
2042 | --[[
2043 | CustomCallbackHelper.CallCallbacks
2044 | (
2045 | CustomCallbacks.MCM_POST_MODIFY_SETTING, --callback id
2046 | nil,
2047 | {currentMenuOption.CurrentSetting, numberToChange}, --args to send
2048 | {currentMenuCategory.Name, currentMenuOption.Attribute} --extra variables
2049 | )
2050 | --]]
2051 | local sound = currentMenuOption.Sound
2052 | if not sound then
2053 | sound = SoundEffect.SOUND_PLOP
2054 | end
2055 | if sound >= 0 then
2056 | sfx:Play(sound, 1, 0, false, 1)
2057 | end
2058 | elseif optionType ~= ModConfigMenu.OptionType.SPACE and pressingButton == "RIGHT" then
2059 | if currentMenuOption.Popup then
2060 | enterPopup = true
2061 | elseif currentMenuOption.OnSelect then
2062 | currentMenuOption.OnSelect()
2063 | end
2064 | end
2065 | end
2066 | end
2067 |
2068 | --confirmed back press
2069 | if pressingButton == "BACK" then
2070 | leaveOptions = true
2071 | end
2072 |
2073 | --confirmed select press
2074 | if pressingButton == "SELECT" then
2075 | if currentMenuOption then
2076 | if currentMenuOption.Popup then
2077 | enterPopup = true
2078 | elseif currentMenuOption.OnSelect then
2079 | currentMenuOption.OnSelect()
2080 | end
2081 | end
2082 | end
2083 |
2084 | --reset command
2085 | if optionChanged then
2086 | if currentMenuOption.Restart or currentMenuOption.Rerun then
2087 | enterPopup = true
2088 | end
2089 | end
2090 | elseif configMenuInSubcategory then
2091 | local hasUsableCategories = false
2092 | if currentMenuCategory.Subcategories then
2093 | for j = 1, #currentMenuCategory.Subcategories do
2094 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2095 | hasUsableCategories = true
2096 | end
2097 | end
2098 | end
2099 | if hasUsableCategories then
2100 | --confirmed down press
2101 | if pressingButton == "DOWN" then
2102 | enterOptions = true
2103 | end
2104 |
2105 | --confirmed up press
2106 | if pressingButton == "UP" then
2107 | leaveSubcategory = true
2108 | end
2109 |
2110 | --confirmed right press
2111 | if pressingButton == "RIGHT" then
2112 | configMenuPositionCursorSubcategory = configMenuPositionCursorSubcategory + 1 --move right down
2113 | end
2114 |
2115 | --confirmed left press
2116 | if pressingButton == "LEFT" then
2117 | configMenuPositionCursorSubcategory = configMenuPositionCursorSubcategory - 1 --move cursor left
2118 | end
2119 |
2120 | --confirmed back press
2121 | if pressingButton == "BACK" then
2122 | leaveSubcategory = true
2123 | end
2124 |
2125 | --confirmed select press
2126 | if pressingButton == "SELECT" then
2127 | enterOptions = true
2128 | end
2129 | end
2130 | else
2131 | --confirmed down press
2132 | if pressingButton == "DOWN" then
2133 | configMenuPositionCursorCategory = configMenuPositionCursorCategory + 1 --move left cursor down
2134 | end
2135 |
2136 | --confirmed up press
2137 | if pressingButton == "UP" then
2138 | configMenuPositionCursorCategory = configMenuPositionCursorCategory - 1 --move left cursor up
2139 | end
2140 |
2141 | --confirmed right press
2142 | if pressingButton == "RIGHT" then
2143 | enterSubcategory = true
2144 | end
2145 |
2146 | --confirmed back press
2147 | if pressingButton == "BACK" then
2148 | ModConfigMenu.CloseConfigMenu()
2149 | end
2150 |
2151 | --confirmed select press
2152 | if pressingButton == "SELECT" then
2153 | enterSubcategory = true
2154 | end
2155 | end
2156 |
2157 | --entering popup
2158 | if enterPopup then
2159 | ModConfigMenu.EnterPopup()
2160 | end
2161 |
2162 | --leaving popup
2163 | if leavePopup then
2164 | ModConfigMenu.LeavePopup()
2165 | end
2166 |
2167 | --entering subcategory
2168 | if enterSubcategory then
2169 | ModConfigMenu.EnterSubcategory()
2170 | end
2171 |
2172 | --entering options
2173 | if enterOptions then
2174 | ModConfigMenu.EnterOptions()
2175 | end
2176 |
2177 | --leaving options
2178 | if leaveOptions then
2179 | ModConfigMenu.LeaveOptions()
2180 | end
2181 |
2182 | --leaving subcategory
2183 | if leaveSubcategory then
2184 | ModConfigMenu.LeaveSubcategory()
2185 | end
2186 |
2187 | --category cursor position was changed
2188 | if lastCursorCategoryPosition ~= configMenuPositionCursorCategory then
2189 | if not configMenuInSubcategory then
2190 | --cursor position
2191 | if configMenuPositionCursorCategory < 1 then --move from the top of the list to the bottom
2192 | configMenuPositionCursorCategory = #ModConfigMenu.MenuData
2193 | end
2194 | if configMenuPositionCursorCategory > #ModConfigMenu.MenuData then --move from the bottom of the list to the top
2195 | configMenuPositionCursorCategory = 1
2196 | end
2197 |
2198 | --make sure subcategory and option positions are 1
2199 | configMenuPositionCursorSubcategory = 1
2200 | configMenuPositionFirstSubcategory = 1
2201 | configMenuPositionCursorOption = 1
2202 | optionsCurrentOffset = 0
2203 | end
2204 | end
2205 |
2206 | --subcategory cursor position was changed
2207 | if lastCursorSubcategoryPosition ~= configMenuPositionCursorSubcategory then
2208 | if not configMenuInOptions then
2209 | --cursor position
2210 | if configMenuPositionCursorSubcategory < 1 then --move from the top of the list to the bottom
2211 | configMenuPositionCursorSubcategory = #currentMenuCategory.Subcategories
2212 | end
2213 | if configMenuPositionCursorSubcategory > #currentMenuCategory.Subcategories then --move from the bottom of the list to the top
2214 | configMenuPositionCursorSubcategory = 1
2215 | end
2216 |
2217 | --first category selection to render
2218 | if configMenuPositionFirstSubcategory > 1 and
2219 | configMenuPositionCursorSubcategory <= configMenuPositionFirstSubcategory + 1 then
2220 | configMenuPositionFirstSubcategory = configMenuPositionCursorSubcategory - 1
2221 | end
2222 | if configMenuPositionFirstSubcategory + (configMenuSubcategoriesCanShow - 1) < #
2223 | currentMenuCategory.Subcategories and
2224 | configMenuPositionCursorSubcategory >= 1 + (configMenuSubcategoriesCanShow - 2) then
2225 | configMenuPositionFirstSubcategory = configMenuPositionCursorSubcategory -
2226 | (configMenuSubcategoriesCanShow - 2
2227 | )
2228 | end
2229 | configMenuPositionFirstSubcategory = math.min(math.max(configMenuPositionFirstSubcategory, 1),
2230 | #currentMenuCategory.Subcategories - (configMenuSubcategoriesCanShow - 1))
2231 |
2232 | --make sure option positions are 1
2233 | configMenuPositionCursorOption = 1
2234 | optionsCurrentOffset = 0
2235 | end
2236 | end
2237 |
2238 | --options cursor position was changed
2239 | if lastCursorOptionsPosition ~= configMenuPositionCursorOption then
2240 | if configMenuInOptions
2241 | and currentMenuSubcategory
2242 | and currentMenuSubcategory.Options
2243 | and #currentMenuSubcategory.Options > 0 then
2244 | --find next valid option that isn't a space
2245 | local nextValidOptionSelection = configMenuPositionCursorOption
2246 | local optionIndex = configMenuPositionCursorOption
2247 | for i = 1, #currentMenuSubcategory.Options * 2 do
2248 | local thisOption = currentMenuSubcategory.Options[optionIndex]
2249 |
2250 | if thisOption
2251 | and thisOption.Type
2252 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
2253 | and
2254 | (
2255 | not thisOption.NoCursorHere or
2256 | (type(thisOption.NoCursorHere) == "function" and not thisOption.NoCursorHere()))
2257 | and thisOption.Display then
2258 | nextValidOptionSelection = optionIndex
2259 |
2260 | break
2261 | end
2262 |
2263 | if configMenuPositionCursorOption > lastCursorOptionsPosition then
2264 | optionIndex = optionIndex + 1
2265 | elseif configMenuPositionCursorOption < lastCursorOptionsPosition then
2266 | optionIndex = optionIndex - 1
2267 | end
2268 | if optionIndex < 1 then
2269 | optionIndex = #currentMenuSubcategory.Options
2270 | end
2271 | if optionIndex > #currentMenuSubcategory.Options then
2272 | optionIndex = 1
2273 | end
2274 | end
2275 |
2276 | configMenuPositionCursorOption = nextValidOptionSelection
2277 |
2278 | updateCurrentMenuVars()
2279 |
2280 | --first options selection to render
2281 | local hasSubcategories = false
2282 | for j = 1, #currentMenuCategory.Subcategories do
2283 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2284 | hasSubcategories = true
2285 | end
2286 | end
2287 | if hasSubcategories then
2288 | --todo
2289 | end
2290 | end
2291 | end
2292 |
2293 | local centerPos = ScreenHelper.GetScreenCenter()
2294 |
2295 | --title pos handling
2296 | local titlePos = centerPos + Vector(68, -118)
2297 |
2298 | --left pos handling
2299 |
2300 | local leftDesiredOffset = 0
2301 | local leftCanScrollUp = false
2302 | local leftCanScrollDown = false
2303 |
2304 | local numLeft = #ModConfigMenu.MenuData
2305 |
2306 | local leftPos = centerPos + Vector(-142, -102)
2307 | local leftPosTopmost = centerPos.Y - 116
2308 | local leftPosBottommost = centerPos.Y + 90
2309 |
2310 | if numLeft > 7 then
2311 | if configMenuPositionCursorCategory > 6 then
2312 | leftCanScrollUp = true
2313 |
2314 | local cursorScroll = configMenuPositionCursorCategory - 6
2315 | local maxLeftScroll = numLeft - 8
2316 | leftDesiredOffset = math.min(cursorScroll, maxLeftScroll) * -14
2317 |
2318 | if cursorScroll < maxLeftScroll then
2319 | leftCanScrollDown = true
2320 | end
2321 | else
2322 | leftCanScrollDown = true
2323 | end
2324 | end
2325 |
2326 | if leftDesiredOffset ~= leftCurrentOffset then
2327 | local modifyOffset = math.floor(leftDesiredOffset - leftCurrentOffset) / 10
2328 | if modifyOffset > -0.1 and modifyOffset < 0 then
2329 | modifyOffset = -0.1
2330 | end
2331 | if modifyOffset < 0.1 and modifyOffset > 0 then
2332 | modifyOffset = 0.1
2333 | end
2334 |
2335 | leftCurrentOffset = leftCurrentOffset + modifyOffset
2336 | if (leftDesiredOffset - leftCurrentOffset) < 0.25 and (leftDesiredOffset - leftCurrentOffset) > -0.25 then
2337 | leftCurrentOffset = leftDesiredOffset
2338 | end
2339 | end
2340 |
2341 | if leftCurrentOffset ~= 0 then
2342 | leftPos = leftPos + Vector(0, leftCurrentOffset)
2343 | end
2344 |
2345 | --options pos handling
2346 | local optionsDesiredOffset = 0
2347 | local optionsCanScrollUp = false
2348 | local optionsCanScrollDown = false
2349 |
2350 | local numOptions = 0
2351 |
2352 | local optionPos = centerPos + Vector(68, -18)
2353 | local optionPosTopmost = centerPos.Y - 108
2354 | local optionPosBottommost = centerPos.Y + 86
2355 |
2356 | if currentMenuSubcategory
2357 | and currentMenuSubcategory.Options
2358 | and #currentMenuSubcategory.Options > 0 then
2359 | numOptions = #currentMenuSubcategory.Options
2360 |
2361 | local hasSubcategories = false
2362 | if currentMenuCategory.Subcategories then
2363 | for j = 1, #currentMenuCategory.Subcategories do
2364 | if currentMenuCategory.Subcategories[j].Name ~= "Uncategorized" then
2365 | numOptions = numOptions + 2
2366 | hasSubcategories = true
2367 | break
2368 | end
2369 | end
2370 | end
2371 |
2372 | if hasSubcategories then
2373 | optionPos = optionPos + Vector(0, -70)
2374 | else
2375 | optionPos = optionPos + Vector(0, math.min(numOptions - 1, 10) * -7)
2376 | end
2377 |
2378 | if numOptions > 12 then
2379 | if configMenuPositionCursorOption > 6 and configMenuInOptions then
2380 | optionsCanScrollUp = true
2381 |
2382 | local cursorScroll = configMenuPositionCursorOption - 6
2383 | local maxOptionsScroll = numOptions - 12
2384 | optionsDesiredOffset = math.min(cursorScroll, maxOptionsScroll) * -14
2385 |
2386 | if cursorScroll < maxOptionsScroll then
2387 | optionsCanScrollDown = true
2388 | end
2389 | else
2390 | optionsCanScrollDown = true
2391 | end
2392 | end
2393 | end
2394 |
2395 | if optionsDesiredOffset ~= optionsCurrentOffset then
2396 | local modifyOffset = math.floor(optionsDesiredOffset - optionsCurrentOffset) / 10
2397 | if modifyOffset > -0.1 and modifyOffset < 0 then
2398 | modifyOffset = -0.1
2399 | end
2400 | if modifyOffset < 0.1 and modifyOffset > 0 then
2401 | modifyOffset = 0.1
2402 | end
2403 |
2404 | optionsCurrentOffset = optionsCurrentOffset + modifyOffset
2405 | if (optionsDesiredOffset - optionsCurrentOffset) < 0.25 and (optionsDesiredOffset - optionsCurrentOffset) > -0.25 then
2406 | optionsCurrentOffset = optionsDesiredOffset
2407 | end
2408 | end
2409 |
2410 | if optionsCurrentOffset ~= 0 then
2411 | optionPos = optionPos + Vector(0, optionsCurrentOffset)
2412 | end
2413 |
2414 | --info pos handling
2415 | local infoPos = centerPos + Vector(-4, 106)
2416 |
2417 | MenuSprite:Render(centerPos, vecZero, vecZero)
2418 |
2419 | --get if controls can be shown
2420 | local shouldShowControls = true
2421 | if configMenuInOptions and currentMenuOption and currentMenuOption.HideControls then
2422 | shouldShowControls = false
2423 | end
2424 | if not ModConfigMenu.Config["Mod Config Menu"].ShowControls then
2425 | shouldShowControls = false
2426 | end
2427 |
2428 | --category
2429 | local lastLeftPos = leftPos
2430 | local renderedLeft = 0
2431 | for categoryIndex = 1, #ModConfigMenu.MenuData do
2432 | --text
2433 | if lastLeftPos.Y > leftPosTopmost and lastLeftPos.Y < leftPosBottommost then
2434 | local textToDraw = tostring(ModConfigMenu.MenuData[categoryIndex].Name)
2435 |
2436 | local color = leftFontColor
2437 | --[[
2438 | if configMenuPositionCursorCategory == categoryIndex then
2439 | color = leftFontColorSelected
2440 | end
2441 | ]]
2442 | local posOffset = Font12:GetStringWidthUTF8(textToDraw) / 2
2443 | Font12:DrawStringUTF8(textToDraw, lastLeftPos.X - posOffset, lastLeftPos.Y - 8, color, 0, true)
2444 |
2445 | --cursor
2446 | if configMenuPositionCursorCategory == categoryIndex then
2447 | CursorSpriteRight:Render(lastLeftPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2448 | end
2449 | end
2450 |
2451 | --increase counter
2452 | renderedLeft = renderedLeft + 1
2453 |
2454 | --pos mod
2455 | lastLeftPos = lastLeftPos + Vector(0, 16)
2456 | end
2457 |
2458 | --render scroll arrows
2459 | if leftCanScrollUp then
2460 | CursorSpriteUp:Render(centerPos + Vector(-78, -104), vecZero, vecZero) --up arrow
2461 | end
2462 | if leftCanScrollDown then
2463 | CursorSpriteDown:Render(centerPos + Vector(-78, 70), vecZero, vecZero) --down arrow
2464 | end
2465 |
2466 | ------------------------
2467 | --RENDER SUBCATEGORIES--
2468 | ------------------------
2469 |
2470 | local lastOptionPos = optionPos
2471 | local renderedOptions = 0
2472 |
2473 | if currentMenuCategory then
2474 | local hasUncategorizedCategory = false
2475 | local hasSubcategories = false
2476 | local numCategories = 0
2477 | for j = 1, #currentMenuCategory.Subcategories do
2478 | if currentMenuCategory.Subcategories[j].Name == "Uncategorized" then
2479 | hasUncategorizedCategory = true
2480 | else
2481 | hasSubcategories = true
2482 | numCategories = numCategories + 1
2483 | end
2484 | end
2485 |
2486 | if hasSubcategories then
2487 | if hasUncategorizedCategory then
2488 | numCategories = numCategories + 1
2489 | end
2490 |
2491 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2492 | local lastSubcategoryPos = optionPos
2493 | if numCategories == 2 then
2494 | lastSubcategoryPos = lastOptionPos + Vector(-38, 0)
2495 | elseif numCategories >= 3 then
2496 | lastSubcategoryPos = lastOptionPos + Vector(-76, 0)
2497 | end
2498 |
2499 | local renderedSubcategories = 0
2500 |
2501 | for subcategoryIndex = 1, #currentMenuCategory.Subcategories do
2502 | if subcategoryIndex >= configMenuPositionFirstSubcategory then
2503 | local thisSubcategory = currentMenuCategory.Subcategories[subcategoryIndex]
2504 |
2505 | local posOffset = 0
2506 |
2507 | if thisSubcategory.Name then
2508 | local textToDraw = thisSubcategory.Name
2509 |
2510 | textToDraw = tostring(textToDraw)
2511 |
2512 | local color = subcategoryFontColor
2513 | if not configMenuInSubcategory then
2514 | color = subcategoryFontColorAlpha
2515 | --[[
2516 | elseif configMenuPositionCursorSubcategory == subcategoryIndex and configMenuInSubcategory then
2517 | color = subcategoryFontColorSelected
2518 | ]]
2519 | end
2520 |
2521 | posOffset = Font12:GetStringWidthUTF8(textToDraw) / 2
2522 | Font12:DrawStringUTF8(textToDraw, lastSubcategoryPos.X - posOffset, lastSubcategoryPos.Y - 8, color, 0, true)
2523 | end
2524 |
2525 | --cursor
2526 | if configMenuPositionCursorSubcategory == subcategoryIndex and configMenuInSubcategory then
2527 | CursorSpriteRight:Render(lastSubcategoryPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2528 | end
2529 |
2530 | --increase counter
2531 | renderedSubcategories = renderedSubcategories + 1
2532 |
2533 | if renderedSubcategories >= configMenuSubcategoriesCanShow then --if this is the last one we should render
2534 | --render scroll arrows
2535 | if configMenuPositionFirstSubcategory > 1 then --if the first one we rendered wasn't the first in the list
2536 | SubcategoryCursorSpriteLeft:Render(lastOptionPos + Vector(-125, 0), vecZero, vecZero)
2537 | end
2538 |
2539 | if subcategoryIndex < #currentMenuCategory.Subcategories then --if this is not the last thing
2540 | SubcategoryCursorSpriteRight:Render(lastOptionPos + Vector(125, 0), vecZero, vecZero)
2541 | end
2542 |
2543 | break
2544 | end
2545 |
2546 | --pos mod
2547 | lastSubcategoryPos = lastSubcategoryPos + Vector(76, 0)
2548 | end
2549 | end
2550 | end
2551 |
2552 | --subcategory selection counts as an option that gets rendered
2553 | renderedOptions = renderedOptions + 1
2554 | lastOptionPos = lastOptionPos + Vector(0, 14)
2555 |
2556 | --subcategory to options divider
2557 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2558 | SubcategoryDividerSprite:Render(lastOptionPos, vecZero, vecZero)
2559 | end
2560 |
2561 | --subcategory to options divider counts as an option that gets rendered
2562 | renderedOptions = renderedOptions + 1
2563 | lastOptionPos = lastOptionPos + Vector(0, 14)
2564 | end
2565 | end
2566 |
2567 | ------------------
2568 | --RENDER OPTIONS--
2569 | ------------------
2570 |
2571 | local firstOptionPos = lastOptionPos
2572 |
2573 | if currentMenuSubcategory
2574 | and currentMenuSubcategory.Options
2575 | and #currentMenuSubcategory.Options > 0 then
2576 | for optionIndex = 1, #currentMenuSubcategory.Options do
2577 | local thisOption = currentMenuSubcategory.Options[optionIndex]
2578 |
2579 | local cursorIsAtThisOption = configMenuPositionCursorOption == optionIndex and configMenuInOptions
2580 | local posOffset = 10
2581 |
2582 | if lastOptionPos.Y > optionPosTopmost and lastOptionPos.Y < optionPosBottommost then
2583 | if thisOption.Type
2584 | and thisOption.Type ~= ModConfigMenu.OptionType.SPACE
2585 | and thisOption.Display then
2586 | local optionType = thisOption.Type
2587 | local optionDisplay = thisOption.Display
2588 | local optionColor = thisOption.Color
2589 |
2590 | local useAltSlider = thisOption.AltSlider
2591 |
2592 | --get what to draw
2593 | if optionType == ModConfigMenu.OptionType.TEXT
2594 | or optionType == ModConfigMenu.OptionType.BOOLEAN
2595 | or optionType == ModConfigMenu.OptionType.NUMBER
2596 | or optionType == ModConfigMenu.OptionType.KEYBIND_KEYBOARD
2597 | or optionType == ModConfigMenu.OptionType.KEYBIND_CONTROLLER
2598 | or optionType == ModConfigMenu.OptionType.TITLE then
2599 | local textToDraw = optionDisplay
2600 |
2601 | if type(optionDisplay) == "function" then
2602 | textToDraw = optionDisplay(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
2603 | end
2604 |
2605 | textToDraw = tostring(textToDraw)
2606 |
2607 | local heightOffset = 6
2608 | local font = Font10
2609 | local color = optionsFontColor
2610 | if not configMenuInOptions then
2611 | if thisOption.NoCursorHere then
2612 | color = optionsFontColorNoCursorAlpha
2613 | else
2614 | color = optionsFontColorAlpha
2615 | end
2616 | elseif thisOption.NoCursorHere then
2617 | color = optionsFontColorNoCursor
2618 | end
2619 | if optionType == ModConfigMenu.OptionType.TITLE then
2620 | heightOffset = 8
2621 | font = Font12
2622 | color = optionsFontColorTitle
2623 | if not configMenuInOptions then
2624 | color = optionsFontColorTitleAlpha
2625 | end
2626 | end
2627 |
2628 | if optionColor then
2629 | color = KColor(optionColor[1], optionColor[2], optionColor[3], color.Alpha)
2630 | end
2631 |
2632 | posOffset = font:GetStringWidthUTF8(textToDraw) / 2
2633 | font:DrawStringUTF8(textToDraw, lastOptionPos.X - posOffset, lastOptionPos.Y - heightOffset, color, 0, true)
2634 | elseif optionType == ModConfigMenu.OptionType.SCROLL then
2635 | local numberToShow = optionDisplay
2636 |
2637 | if type(optionDisplay) == "function" then
2638 | numberToShow = optionDisplay(cursorIsAtThisOption, configMenuInOptions, lastOptionPos)
2639 | end
2640 |
2641 | posOffset = 31
2642 | local scrollOffset = 0
2643 |
2644 | if type(numberToShow) == "number" then
2645 | numberToShow = math.max(math.min(math.floor(numberToShow), 10), 0)
2646 | elseif type(numberToShow) == "string" then
2647 | local numberToShowStart, numberToShowEnd = string.find(numberToShow, "$scroll")
2648 | if numberToShowStart and numberToShowEnd then
2649 | local numberStart = numberToShowEnd + 1
2650 | local numberEnd = numberToShowEnd + 3
2651 | local numberString = string.sub(numberToShow, numberStart, numberEnd)
2652 | numberString = tonumber(numberString)
2653 | if not numberString or (numberString and not type(numberString) == "number") or
2654 | (numberString and type(numberString) == "number" and numberString < 10) then
2655 | numberEnd = numberEnd - 1
2656 | numberString = string.sub(numberToShow, numberStart, numberEnd)
2657 | numberString = tonumber(numberString)
2658 | end
2659 | if numberString and type(numberString) == "number" then
2660 | local textToDrawPreScroll = string.sub(numberToShow, 0, numberToShowStart - 1)
2661 | local textToDrawPostScroll = string.sub(numberToShow, numberEnd, string.len(numberToShow))
2662 | local textToDraw = textToDrawPreScroll .. " " .. textToDrawPostScroll
2663 |
2664 | local color = optionsFontColor
2665 | if not configMenuInOptions then
2666 | color = optionsFontColorAlpha
2667 | end
2668 | if optionColor then
2669 | color = KColor(optionColor[1], optionColor[2], optionColor[3], color.Alpha)
2670 | end
2671 |
2672 | scrollOffset = posOffset
2673 | posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2674 | Font10:DrawStringUTF8(textToDraw, lastOptionPos.X - posOffset, lastOptionPos.Y - 6, color, 0, true)
2675 |
2676 | scrollOffset = posOffset - (Font10:GetStringWidthUTF8(textToDrawPreScroll) + scrollOffset)
2677 | numberToShow = numberString
2678 | end
2679 | end
2680 | end
2681 |
2682 | local scrollColor = optionsSpriteColor
2683 | if not configMenuInOptions then
2684 | scrollColor = optionsSpriteColorAlpha
2685 | end
2686 | if optionColor then
2687 | scrollColor = Color(optionColor[1], optionColor[2], optionColor[3], scrollColor.A, scrollColor.RO,
2688 | scrollColor.GO
2689 | , scrollColor.BO)
2690 | end
2691 |
2692 | local sliderString = "Slider1"
2693 | if useAltSlider then
2694 | sliderString = "Slider2"
2695 | end
2696 |
2697 | SliderSprite.Color = scrollColor
2698 | SliderSprite:SetFrame(sliderString, numberToShow)
2699 | SliderSprite:Render(lastOptionPos - Vector(scrollOffset, -2), vecZero, vecZero)
2700 | end
2701 |
2702 | local showStrikeout = thisOption.ShowStrikeout
2703 | if posOffset > 0 and (type(showStrikeout) == "boolean" and showStrikeout == true) or
2704 | (type(showStrikeout) == "function" and showStrikeout() == true) then
2705 | if configMenuInOptions then
2706 | StrikeOutSprite.Color = colorDefault
2707 | else
2708 | StrikeOutSprite.Color = colorHalf
2709 | end
2710 | StrikeOutSprite:SetFrame("Strikeout", math.floor(posOffset))
2711 | StrikeOutSprite:Render(lastOptionPos, vecZero, vecZero)
2712 | end
2713 | end
2714 |
2715 | --cursor
2716 | if cursorIsAtThisOption then
2717 | CursorSpriteRight:Render(lastOptionPos + Vector((posOffset + 10) * -1, 0), vecZero, vecZero)
2718 | end
2719 | end
2720 |
2721 | --increase counter
2722 | renderedOptions = renderedOptions + 1
2723 |
2724 | --pos mod
2725 | lastOptionPos = lastOptionPos + Vector(0, 14)
2726 | end
2727 |
2728 | --render scroll arrows
2729 | if optionsCanScrollUp then
2730 | OptionsCursorSpriteUp:Render(centerPos + Vector(193, -86), vecZero, vecZero) --up arrow
2731 | end
2732 | if optionsCanScrollDown then
2733 | local yPos = 66
2734 | if shouldShowControls then
2735 | yPos = 40
2736 | end
2737 |
2738 | OptionsCursorSpriteDown:Render(centerPos + Vector(193, yPos), vecZero, vecZero) --down arrow
2739 | end
2740 | end
2741 |
2742 | MenuOverlaySprite:Render(centerPos, vecZero, vecZero)
2743 |
2744 | --title
2745 | local titleText = "Mod Config Menu"
2746 | if configMenuInSubcategory then
2747 | titleText = tostring(currentMenuCategory.Name)
2748 | end
2749 | local titleTextOffset = Font16Bold:GetStringWidthUTF8(titleText) / 2
2750 | Font16Bold:DrawStringUTF8(titleText, titlePos.X - titleTextOffset, titlePos.Y - 9, mainFontColor, 0, true)
2751 |
2752 | --info
2753 | local infoTable = nil
2754 | local isOldInfo = false
2755 |
2756 | if configMenuInOptions then
2757 | if currentMenuOption and currentMenuOption.Info then
2758 | infoTable = currentMenuOption.Info
2759 | end
2760 | elseif configMenuInSubcategory then
2761 | if currentMenuSubcategory and currentMenuSubcategory.Info then
2762 | infoTable = currentMenuSubcategory.Info
2763 | end
2764 | elseif currentMenuCategory and currentMenuCategory.Info then
2765 | infoTable = currentMenuCategory.Info
2766 | if currentMenuCategory.IsOld then
2767 | isOldInfo = true
2768 | end
2769 | end
2770 |
2771 | if infoTable then
2772 | local lineWidth = 340
2773 | if shouldShowControls then
2774 | lineWidth = 260
2775 | end
2776 |
2777 | local infoTableDisplay = ModConfigMenu.ConvertDisplayToTextTable(infoTable, lineWidth, Font10)
2778 |
2779 | local lastInfoPos = infoPos - Vector(0, 6 * #infoTableDisplay)
2780 | for line = 1, #infoTableDisplay do
2781 | --text
2782 | local textToDraw = tostring(infoTableDisplay[line])
2783 | local posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2784 | local color = mainFontColor
2785 | if isOldInfo then
2786 | color = optionsFontColorTitle
2787 | end
2788 | Font10:DrawStringUTF8(textToDraw, lastInfoPos.X - posOffset, lastInfoPos.Y - 6, color, 0, true)
2789 |
2790 | --pos mod
2791 | lastInfoPos = lastInfoPos + Vector(0, 10)
2792 | end
2793 | end
2794 |
2795 | --hud offset
2796 | if configMenuInOptions
2797 | and currentMenuOption
2798 | and currentMenuOption.ShowOffset
2799 | and ScreenHelper then
2800 | --render the visual
2801 | HudOffsetVisualBottomRight:Render(ScreenHelper.GetScreenBottomRight(), vecZero, vecZero)
2802 | HudOffsetVisualBottomLeft:Render(ScreenHelper.GetScreenBottomLeft(), vecZero, vecZero)
2803 | HudOffsetVisualTopRight:Render(ScreenHelper.GetScreenTopRight(), vecZero, vecZero)
2804 | HudOffsetVisualTopLeft:Render(ScreenHelper.GetScreenTopLeft(), vecZero, vecZero)
2805 | end
2806 |
2807 | --popup
2808 | if configMenuInPopup
2809 | and currentMenuOption
2810 | and (currentMenuOption.Popup or currentMenuOption.Restart or currentMenuOption.Rerun) then
2811 | PopupSprite:Render(centerPos, vecZero, vecZero)
2812 |
2813 | local popupTable = currentMenuOption.Popup
2814 |
2815 | if not popupTable then
2816 | if currentMenuOption.Restart then
2817 | popupTable = "Restart the game for this setting to take effect"
2818 | end
2819 |
2820 | if currentMenuOption.Rerun then
2821 | popupTable = "Start a new run for this setting to take effect"
2822 | end
2823 | end
2824 |
2825 | if popupTable then
2826 | local lineWidth = currentMenuOption.PopupWidth or 180
2827 |
2828 | local popupTableDisplay = ModConfigMenu.ConvertDisplayToTextTable(popupTable, lineWidth, Font10)
2829 |
2830 | local lastPopupPos = (centerPos + Vector(0, 2)) - Vector(0, 6 * #popupTableDisplay)
2831 | for line = 1, #popupTableDisplay do
2832 | --text
2833 | local textToDraw = tostring(popupTableDisplay[line])
2834 | local posOffset = Font10:GetStringWidthUTF8(textToDraw) / 2
2835 | Font10:DrawStringUTF8(textToDraw, lastPopupPos.X - posOffset, lastPopupPos.Y - 6, mainFontColor, 0, true)
2836 |
2837 | --pos mod
2838 | lastPopupPos = lastPopupPos + Vector(0, 10)
2839 | end
2840 | end
2841 | end
2842 |
2843 | --controls
2844 | if shouldShowControls then
2845 | --back
2846 | local bottomLeft = ScreenHelper.GetScreenBottomLeft(0)
2847 | if not configMenuInSubcategory then
2848 | CornerExit:Render(bottomLeft, vecZero, vecZero)
2849 | else
2850 | CornerBack:Render(bottomLeft, vecZero, vecZero)
2851 | end
2852 |
2853 | local goBackString = ""
2854 | if ModConfigMenu.Config.LastBackPressed then
2855 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed] then
2856 | goBackString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastBackPressed]
2857 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed] then
2858 | goBackString = InputHelper.ControllerToString[ModConfigMenu.Config.LastBackPressed]
2859 | end
2860 | end
2861 | Font10:DrawStringUTF8(goBackString, (bottomLeft.X - Font10:GetStringWidthUTF8(goBackString) / 2) + 36,
2862 | bottomLeft.Y - 24,
2863 | mainFontColor, 0, true)
2864 |
2865 | --select
2866 | local bottomRight = ScreenHelper.GetScreenBottomRight(0)
2867 | if not configMenuInPopup then
2868 | local foundValidPopup = false
2869 | --[[
2870 | if configMenuInSubcategory
2871 | and configMenuInOptions
2872 | and currentMenuOption
2873 | and currentMenuOption.Type
2874 | and currentMenuOption.Type ~= ModConfigMenu.OptionType.SPACE
2875 | and currentMenuOption.Popup then
2876 | foundValidPopup = true
2877 | end
2878 | ]]
2879 | if foundValidPopup then
2880 | CornerOpen:Render(bottomRight, vecZero, vecZero)
2881 | else
2882 | CornerSelect:Render(bottomRight, vecZero, vecZero)
2883 | end
2884 |
2885 | local selectString = ""
2886 | if ModConfigMenu.Config.LastSelectPressed then
2887 | if InputHelper.KeyboardToString[ModConfigMenu.Config.LastSelectPressed] then
2888 | selectString = InputHelper.KeyboardToString[ModConfigMenu.Config.LastSelectPressed]
2889 | elseif InputHelper.ControllerToString[ModConfigMenu.Config.LastSelectPressed] then
2890 | selectString = InputHelper.ControllerToString[ModConfigMenu.Config.LastSelectPressed]
2891 | end
2892 | end
2893 | Font10:DrawStringUTF8(selectString, (bottomRight.X - Font10:GetStringWidthUTF8(selectString) / 2) - 36,
2894 | bottomRight.Y - 24, mainFontColor, 0, true)
2895 | end
2896 | end
2897 | else
2898 | for i = 0, game:GetNumPlayers() - 1 do
2899 | local player = Isaac.GetPlayer(i)
2900 | local data = player:GetData()
2901 |
2902 | --enable player controls
2903 | if data.ConfigMenuPlayerPosition then
2904 | data.ConfigMenuPlayerPosition = nil
2905 | end
2906 | if data.ConfigMenuPlayerControlsDisabled then
2907 | player.ControlsEnabled = true
2908 | data.ConfigMenuPlayerControlsDisabled = false
2909 | end
2910 | end
2911 |
2912 | configMenuInSubcategory = false
2913 | configMenuInOptions = false
2914 | configMenuInPopup = false
2915 |
2916 | holdingCounterDown = 0
2917 | holdingCounterUp = 0
2918 | holdingCounterLeft = 0
2919 | holdingCounterRight = 0
2920 |
2921 | configMenuPositionCursorCategory = 1
2922 | configMenuPositionCursorSubcategory = 1
2923 | configMenuPositionCursorOption = 1
2924 |
2925 | configMenuPositionFirstSubcategory = 1
2926 |
2927 | leftCurrentOffset = 0
2928 | optionsCurrentOffset = 0
2929 | end
2930 | end
2931 |
2932 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_POST_RENDER, ModConfigMenu.PostRender)
2933 |
2934 | function ModConfigMenu.OpenConfigMenu()
2935 | if ModConfigMenu.RoomIsSafe() then
2936 | if ModConfigMenu.Config["Mod Config Menu"].HideHudInMenu then
2937 | local game = Game()
2938 | if REPENTANCE then
2939 | local hud = game:GetHUD()
2940 | hud:SetVisible(false)
2941 | else
2942 | local seeds = game:GetSeeds()
2943 | seeds:AddSeedEffect(SeedEffect.SEED_NO_HUD)
2944 | end
2945 | end
2946 |
2947 | ModConfigMenu.IsVisible = true
2948 | else
2949 | local sfx = SFXManager()
2950 | sfx:Play(SoundEffect.SOUND_BOSS2INTRO_ERRORBUZZ, 0.75, 0, false, 1)
2951 | end
2952 | end
2953 |
2954 | function ModConfigMenu.CloseConfigMenu()
2955 | ModConfigMenu.LeavePopup()
2956 | ModConfigMenu.LeaveOptions()
2957 | ModConfigMenu.LeaveSubcategory()
2958 |
2959 | local game = Game()
2960 | if REPENTANCE then
2961 | local hud = game:GetHUD()
2962 | hud:SetVisible(true)
2963 | else
2964 | local seeds = game:GetSeeds()
2965 | seeds:RemoveSeedEffect(SeedEffect.SEED_NO_HUD)
2966 | end
2967 |
2968 |
2969 | ModConfigMenu.IsVisible = false
2970 | end
2971 |
2972 | function ModConfigMenu.ToggleConfigMenu()
2973 | if ModConfigMenu.IsVisible then
2974 | ModConfigMenu.CloseConfigMenu()
2975 | else
2976 | ModConfigMenu.OpenConfigMenu()
2977 | end
2978 | end
2979 |
2980 | function ModConfigMenu.InputAction(_, entity, inputHook, buttonAction)
2981 | if ModConfigMenu.IsVisible and buttonAction ~= ButtonAction.ACTION_FULLSCREEN and
2982 | buttonAction ~= ButtonAction.ACTION_CONSOLE then
2983 | if inputHook == InputHook.IS_ACTION_PRESSED or inputHook == InputHook.IS_ACTION_TRIGGERED then
2984 | return false
2985 | else
2986 | return 0
2987 | end
2988 | end
2989 | end
2990 |
2991 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_INPUT_ACTION, ModConfigMenu.InputAction)
2992 |
2993 | --console commands that toggle the menu
2994 | local toggleCommands = {
2995 | ["modconfigmenu"] = true,
2996 | ["modconfig"] = true,
2997 | ["mcm"] = true,
2998 | ["mc"] = true
2999 | }
3000 | function ModConfigMenu.ExecuteCmd(_, command, args)
3001 | command = command:lower()
3002 |
3003 | if toggleCommands[command] then
3004 | ModConfigMenu.ToggleConfigMenu()
3005 | end
3006 | end
3007 |
3008 | ModConfigMenu.Mod:AddCallback(ModCallbacks.MC_EXECUTE_CMD, ModConfigMenu.ExecuteCmd)
3009 |
3010 | if ModConfigMenu.StandaloneMod then
3011 | if not ModConfigMenu.StandaloneSaveLoaded then
3012 | ModConfigMenu.StandaloneSaveLoaded = true
3013 | end
3014 | end
3015 |
3016 | function ModConfigMenu.GetCurrentFocus()
3017 | return {
3018 | category = currentMenuCategory,
3019 | subcategory = currentMenuSubcategory,
3020 | option = currentMenuOption
3021 | }
3022 | end
3023 |
3024 | ------------
3025 | --FINISHED--
3026 | ------------
3027 | Isaac.DebugString("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loaded.")
3028 | print("Mod Config Menu v" .. tostring(ModConfigMenu.Version) .. " - Loaded.")
3029 |
3030 | return ModConfigMenu
3031 |
--------------------------------------------------------------------------------