Skip to content

Commit 6eac886

Browse files
authored
Implement dynamic safe mode (#6)
* initial impl * update optional api * update workflow lol * extra hook listener for good measure * initial impl * update optional api * extra hook listener for good measure * add cheat info * adjust cheat options
1 parent a7beb2d commit 6eac886

32 files changed

Lines changed: 176 additions & 19 deletions

include/horrible/API.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ namespace horrible {
3434
geode::utils::StringMap<std::shared_ptr<Option>> m_options; // Map of registered options
3535
std::vector<std::string> m_categories; // Array of auto-registered categories
3636

37+
std::unordered_map<std::string_view, std::weak_ptr<Option>> m_enabledCheats; // Map of currently enabled cheat options, used for dynamic safe mode
38+
3739
geode::utils::StringMap<const geode::Mod* const> m_integrations; // Map of auto-registered external mods using this API
3840

3941
std::unordered_map<std::string_view, std::vector<Callback>> m_delegates; // Map of option ID to array of delegates to call when that option is toggled
@@ -91,6 +93,13 @@ namespace horrible {
9193
*/
9294
void registerOption(std::shared_ptr<Option> option);
9395

96+
/**
97+
* Check if a cheat option is currently enabled
98+
*
99+
* @returns Whether cheating is on
100+
*/
101+
bool isCheatEnabled() const noexcept;
102+
94103
/**
95104
* Returns a reference to the array of all registered options
96105
*
@@ -125,6 +134,15 @@ namespace horrible {
125134
*/
126135
[[nodiscard]] bool isViewed(geode::ZStringView id) const;
127136

137+
/**
138+
* Quickly check if an option is a cheat option
139+
*
140+
* @param id The ID of the option to check
141+
*
142+
* @returns Boolean of whether this option is a cheat or not
143+
*/
144+
[[nodiscard]] bool isCheating(geode::ZStringView id) const;
145+
128146
/**
129147
* Quickly check the default toggle state of an option
130148
*
@@ -161,6 +179,13 @@ namespace horrible {
161179
*/
162180
[[nodiscard]] size_t getDelegateCount(std::string_view id) const noexcept;
163181

182+
/**
183+
* Check if Safe Mode should be enabled based on the current state of options and settings
184+
*
185+
* @returns Whether Safe Mode should be enabled or not
186+
*/
187+
[[nodiscard]] bool shouldBeSafeMode() const noexcept;
188+
164189
/**
165190
* Set the toggle state of an option
166191
*

include/horrible/Events.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ namespace horrible {
1313
using ThreadSafeGlobalEvent::ThreadSafeGlobalEvent;
1414
};
1515

16+
struct OptionCheatingEvent final : public geode::Event<OptionEvent, bool(bool)> {
17+
using Event::Event;
18+
};
19+
1620
inline geode::ListenerHandle* listenForHorribleOptionChanges(std::string id, geode::CopyableFunction<void(HorribleOptionSave)>&& callback) {
1721
return OptionEvent(std::move(id)).listen(std::move(callback)).leak();
1822
};

include/horrible/Option.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ namespace horrible {
3939
bool m_online = false; // If the option requires an active internet connection to work properly
4040
bool m_restart = false; // If the option requires a game restart to take effect
4141
std::vector<Platform> m_platforms = {Platform::All}; // Platforms that the option supports
42+
bool m_isCheating = false; // If the option counts as cheating and will trigger dynamic safe mode
4243
const geode::Mod* const m_integration = nullptr; // External mod that registered this option
4344

4445
public:
@@ -60,6 +61,7 @@ namespace horrible {
6061
std::shared_ptr<Option> setOnline(bool online);
6162
std::shared_ptr<Option> setRequiresRestart(bool required);
6263
std::shared_ptr<Option> setSupportedPlatforms(std::vector<Platform> platforms);
64+
std::shared_ptr<Option> setCheating(bool cheat);
6365

6466
std::shared_ptr<Option> autoRegister();
6567

@@ -72,6 +74,7 @@ namespace horrible {
7274
[[nodiscard]] bool isOnline() const noexcept;
7375
[[nodiscard]] bool isRestartRequired() const noexcept;
7476
[[nodiscard]] std::span<const Platform> getSupportedPlatforms() const noexcept;
77+
[[nodiscard]] bool isCheating() const noexcept;
7578
[[nodiscard]] const geode::Mod* getIntegration() const noexcept;
7679

7780
void enable() &;

include/horrible/OptionalAPI.hpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace horrible {
2828
bool online; // If the option requires an active internet connection to work properly
2929
bool restart; // If the option requires a game restart to take effect
3030
std::vector<Platform> platforms; // Platforms that the option supports
31+
bool cheating; // If the option counts as cheating and will trigger dynamic safe mode
3132

3233
OptionV2() = default; // Default constructor
3334

@@ -41,7 +42,8 @@ namespace horrible {
4142
bool state = false,
4243
bool online = false,
4344
bool restart = false,
44-
std::vector<Platform> platforms = {Platform::All}) :
45+
std::vector<Platform> platforms = {Platform::All},
46+
bool cheating = false) :
4547
id(std::move(id)),
4648
name(std::move(name)),
4749
description(std::move(description)),
@@ -51,6 +53,7 @@ namespace horrible {
5153
online(online),
5254
restart(restart),
5355
platforms(std::move(platforms)),
56+
cheating(cheating),
5457
integration(geode::Mod::get()) {};
5558

5659
inline const geode::Mod* getIntegration() const noexcept {

mod.json

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@
4747
}
4848
},
4949
"settings": {
50-
"safe-mode": {
51-
"type": "bool",
52-
"name": "Safe Mode",
53-
"description": "Prevent accidentally saving progress made in levels while using this mod's features. <co>We recommend keeping this enabled while this mod is active!</cr>",
54-
"default": true
55-
},
5650
"button": {
5751
"type": "custom:menu",
5852
"name": " "
@@ -66,6 +60,25 @@
6660
],
6761
"category": "universal"
6862
},
63+
"safe-mode-section": {
64+
"type": "title",
65+
"name": "Safe Mode",
66+
"description": "Settings for Safe Mode."
67+
},
68+
"safe-mode": {
69+
"type": "bool",
70+
"name": "Enable",
71+
"description": "Prevent accidentally saving progress made in levels while using this mod's features. <co>We recommend keeping this enabled while this mod is active!</cr>",
72+
"default": true,
73+
"enable-if": "!dyn-safe-mode",
74+
"enable-if-description": "Disable Automatic Safe Mode to manually change this setting."
75+
},
76+
"dyn-safe-mode": {
77+
"type": "bool",
78+
"name": "Automatic",
79+
"description": "Automatically toggle Safe Mode whenever you toggle any options that are marked as cheats.",
80+
"default": true
81+
},
6982
"ui": {
7083
"type": "title",
7184
"name": "Interface",

src/API.cpp

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ std::shared_ptr<Option> Option::setSupportedPlatforms(std::vector<Platform> plat
7070
return shared_from_this();
7171
};
7272

73+
std::shared_ptr<Option> Option::setCheating(bool isCheat) {
74+
m_isCheating = isCheat;
75+
return shared_from_this();
76+
};
77+
7378
ZStringView Option::getID() const noexcept {
7479
return m_id;
7580
};
@@ -106,6 +111,10 @@ std::span<const Platform> Option::getSupportedPlatforms() const noexcept {
106111
return m_platforms;
107112
};
108113

114+
bool Option::isCheating() const noexcept {
115+
return m_isCheating;
116+
};
117+
109118
const Mod* Option::getIntegration() const noexcept {
110119
return m_integration;
111120
};
@@ -168,8 +177,16 @@ void OptionManager::registerOption(std::shared_ptr<Option> option) {
168177

169178
std::string id = option->getID();
170179

180+
auto cheats = m_enabledCheats.size();
181+
182+
if (option->isCheating()) {
183+
if (isEnabled(id)) m_enabledCheats.emplace(option->getID(), option);
184+
};
185+
171186
m_options.emplace(std::move(id), option);
172187
log::debug("Registered option {} of category {}", option->getID(), option->getCategory());
188+
189+
if (cheats == 0 && m_enabledCheats.size() > cheats) (void)OptionCheatingEvent().send(true);
173190
};
174191
};
175192

@@ -178,6 +195,10 @@ void OptionManager::addDelegate(ZStringView id, Callback&& callback) {
178195
thisDelegate.push_back(std::move(callback));
179196
};
180197

198+
bool OptionManager::isCheatEnabled() const noexcept {
199+
return m_enabledCheats.size() > 0;
200+
};
201+
181202
std::vector<std::weak_ptr<Option>> OptionManager::getOptions() const {
182203
std::vector<std::weak_ptr<Option>> out;
183204
out.reserve(m_options.size());
@@ -212,6 +233,11 @@ bool OptionManager::isViewed(ZStringView id) const {
212233
return getOption(id).viewed;
213234
};
214235

236+
bool OptionManager::isCheating(ZStringView id) const {
237+
if (auto o = getOptionInfo(id).lock()) return o->isCheating();
238+
return false;
239+
};
240+
215241
bool OptionManager::getDefaultToggleState(ZStringView id) const noexcept {
216242
if (auto o = getOptionInfo(id).lock()) return o->getDefaultToggleState();
217243
return false;
@@ -231,6 +257,11 @@ size_t OptionManager::getDelegateCount(std::string_view id) const noexcept {
231257
return 0;
232258
};
233259

260+
bool OptionManager::shouldBeSafeMode() const noexcept {
261+
if (Mod::get()->getSettingValue<bool>("dyn-safe-mode")) return isCheatEnabled();
262+
return Mod::get()->getSettingValue<bool>("safe-mode");
263+
};
264+
234265
void OptionManager::toggleOption(ZStringView id, bool enable) {
235266
setOption(id, enable, isPinned(id));
236267
};
@@ -243,10 +274,30 @@ void OptionManager::setOption(ZStringView id, bool enable, bool pin, bool viewed
243274

244275
log::trace("Called {} delegates {} for option {}", it != m_delegates.end() ? it->second.size() : 0, enable ? "on" : "off", id);
245276

246-
auto save = HorribleOptionSave{enable, pin, viewed};
277+
auto cheats = m_enabledCheats.size();
278+
279+
auto const save = HorribleOptionSave{enable, pin, viewed};
247280

248281
(void)Mod::get()->setSavedValue(id, save);
249282
(void)OptionEvent(id).send(save);
283+
284+
if (auto it = m_enabledCheats.find(id); it != m_enabledCheats.end()) {
285+
if (!enable) m_enabledCheats.erase(it);
286+
} else if (enable) {
287+
if (auto o = getOptionInfo(id).lock()) {
288+
if (o->isCheating()) {
289+
log::debug("Enabled cheat option {}, adding to enabled cheats map", id);
290+
m_enabledCheats.emplace(id, o);
291+
};
292+
};
293+
};
294+
295+
auto cheatsNow = m_enabledCheats.size();
296+
297+
if (isCheating(id) && cheats != cheatsNow) {
298+
if (cheats == 0 && cheatsNow > 0) (void)OptionCheatingEvent().send(true);
299+
if (cheats > 0 && cheatsNow == 0) (void)OptionCheatingEvent().send(false);
300+
};
250301
};
251302

252303
OptionManager* OptionManager::get() noexcept {
@@ -291,7 +342,8 @@ void OptionManagerV2::registerOption(OptionV2 const& option) {
291342
->setDefaultToggleState(option.state)
292343
->setOnline(option.online)
293344
->setRequiresRestart(option.restart)
294-
->setSupportedPlatforms(option.platforms);
345+
->setSupportedPlatforms(option.platforms)
346+
->setCheating(option.cheating);
295347

296348
om->registerOption(opt);
297349
(void)OptionEvent(opt->getID()).send(om->getOption(opt->getID()));

src/Utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ namespace horrible {
7676
// For convenience
7777
namespace setting {
7878
inline constexpr auto SafeMode = "safe-mode";
79+
inline constexpr auto DynamicSafeMode = "dyn-safe-mode";
7980
inline constexpr auto FloatingBtn = "floating-btn";
8081
};
8182

src/hooks/Dementia.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ static auto const o = Option::create(THIS_ID)
1515
->setDescription("Chance for the player to occasionally randomly teleport a few steps back while playing a level.\n<cl>suggested by imdissapearinghelp</c>")
1616
->setCategory(category::misc)
1717
->setSillyTier(SillyTier::Medium)
18+
->setCheating(true)
1819
->autoRegister();
1920

2021
class $modify(DementiaPlayerObject, PlayerObject) {

src/hooks/DoubleJump.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
1414
->setDescription("Allows your character to double-jump in a level.\n<cl>created by Cheeseworks</c>")
1515
->setCategory(category::misc)
1616
->setSillyTier(SillyTier::Low)
17+
->setCheating(true)
1718
->autoRegister();
1819

1920
class $modify(DoubleJumpPlayerObject, PlayerObject) {

src/hooks/Gambler.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
1414
->setDescription("When reaching 95% in a level, you have a 50/50 chance at randomly being blasted way far back.\n<cl>suggested by Timered</c>")
1515
->setCategory(category::misc)
1616
->setSillyTier(SillyTier::High)
17+
->setCheating(true)
1718
->autoRegister();
1819

1920
class $modify(GamblerPlayLayer, PlayLayer) {

0 commit comments

Comments
 (0)