Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- "**"
- "main"

jobs:
build:
Expand Down Expand Up @@ -113,5 +113,5 @@ jobs:
embed-url: ${{ steps.publish-nightly.outputs.url }}
embed-author: ${{ github.actor }}
embed-author-icon: "https://avatars.githubusercontent.com/u/${{ github.actor_id }}?v=4"
embed-thumbnail: "https://raw.githubusercontent.com/${{ github.repository }}/refs/tags/nightly/logo.png"
embed-thumbnail: "https://raw.githubusercontent.com/${{ github.repository }}/refs/tags/dev/logo.png"
embed-timestamp: "now"
25 changes: 25 additions & 0 deletions include/horrible/API.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ namespace horrible {
geode::utils::StringMap<std::shared_ptr<Option>> m_options; // Map of registered options
std::vector<std::string> m_categories; // Array of auto-registered categories

std::unordered_map<std::string_view, std::weak_ptr<Option>> m_enabledCheats; // Map of currently enabled cheat options, used for dynamic safe mode

geode::utils::StringMap<const geode::Mod* const> m_integrations; // Map of auto-registered external mods using this API

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
Expand Down Expand Up @@ -91,6 +93,13 @@ namespace horrible {
*/
void registerOption(std::shared_ptr<Option> option);

/**
* Check if a cheat option is currently enabled
*
* @returns Whether cheating is on
*/
bool isCheatEnabled() const noexcept;

/**
* Returns a reference to the array of all registered options
*
Expand Down Expand Up @@ -125,6 +134,15 @@ namespace horrible {
*/
[[nodiscard]] bool isViewed(geode::ZStringView id) const;

/**
* Quickly check if an option is a cheat option
*
* @param id The ID of the option to check
*
* @returns Boolean of whether this option is a cheat or not
*/
[[nodiscard]] bool isCheating(geode::ZStringView id) const;

/**
* Quickly check the default toggle state of an option
*
Expand Down Expand Up @@ -161,6 +179,13 @@ namespace horrible {
*/
[[nodiscard]] size_t getDelegateCount(std::string_view id) const noexcept;

/**
* Check if Safe Mode should be enabled based on the current state of options and settings
*
* @returns Whether Safe Mode should be enabled or not
*/
[[nodiscard]] bool shouldBeSafeMode() const noexcept;

/**
* Set the toggle state of an option
*
Expand Down
4 changes: 4 additions & 0 deletions include/horrible/Events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ namespace horrible {
using ThreadSafeGlobalEvent::ThreadSafeGlobalEvent;
};

struct OptionCheatingEvent final : public geode::Event<OptionEvent, bool(bool)> {
using Event::Event;
};

inline geode::ListenerHandle* listenForHorribleOptionChanges(std::string id, geode::CopyableFunction<void(HorribleOptionSave)>&& callback) {
return OptionEvent(std::move(id)).listen(std::move(callback)).leak();
};
Expand Down
3 changes: 3 additions & 0 deletions include/horrible/Option.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ namespace horrible {
bool m_online = false; // If the option requires an active internet connection to work properly
bool m_restart = false; // If the option requires a game restart to take effect
std::vector<Platform> m_platforms = {Platform::All}; // Platforms that the option supports
bool m_isCheating = false; // If the option counts as cheating and will trigger dynamic safe mode
const geode::Mod* const m_integration = nullptr; // External mod that registered this option

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

std::shared_ptr<Option> autoRegister();

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

void enable() &;
Expand Down
5 changes: 4 additions & 1 deletion include/horrible/OptionalAPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace horrible {
bool online; // If the option requires an active internet connection to work properly
bool restart; // If the option requires a game restart to take effect
std::vector<Platform> platforms; // Platforms that the option supports
bool cheating; // If the option counts as cheating and will trigger dynamic safe mode

OptionV2() = default; // Default constructor

Expand All @@ -41,7 +42,8 @@ namespace horrible {
bool state = false,
bool online = false,
bool restart = false,
std::vector<Platform> platforms = {Platform::All}) :
std::vector<Platform> platforms = {Platform::All},
bool cheating = false) :
id(std::move(id)),
name(std::move(name)),
description(std::move(description)),
Expand All @@ -51,6 +53,7 @@ namespace horrible {
online(online),
restart(restart),
platforms(std::move(platforms)),
cheating(cheating),
integration(geode::Mod::get()) {};

inline const geode::Mod* getIntegration() const noexcept {
Expand Down
25 changes: 19 additions & 6 deletions mod.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,6 @@
}
},
"settings": {
"safe-mode": {
"type": "bool",
"name": "Safe Mode",
"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>",
"default": true
},
"button": {
"type": "custom:menu",
"name": " "
Expand All @@ -66,6 +60,25 @@
],
"category": "universal"
},
"safe-mode-section": {
"type": "title",
"name": "Safe Mode",
"description": "Settings for Safe Mode."
},
"safe-mode": {
"type": "bool",
"name": "Enable",
"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>",
"default": true,
"enable-if": "!dyn-safe-mode",
"enable-if-description": "Disable Automatic Safe Mode to manually change this setting."
},
"dyn-safe-mode": {
"type": "bool",
"name": "Automatic",
"description": "Automatically toggle Safe Mode whenever you toggle any options that are marked as cheats.",
"default": true
},
"ui": {
"type": "title",
"name": "Interface",
Expand Down
56 changes: 54 additions & 2 deletions src/API.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ std::shared_ptr<Option> Option::setSupportedPlatforms(std::vector<Platform> plat
return shared_from_this();
};

std::shared_ptr<Option> Option::setCheating(bool isCheat) {
m_isCheating = isCheat;
return shared_from_this();
};

ZStringView Option::getID() const noexcept {
return m_id;
};
Expand Down Expand Up @@ -106,6 +111,10 @@ std::span<const Platform> Option::getSupportedPlatforms() const noexcept {
return m_platforms;
};

bool Option::isCheating() const noexcept {
return m_isCheating;
};

const Mod* Option::getIntegration() const noexcept {
return m_integration;
};
Expand Down Expand Up @@ -168,8 +177,16 @@ void OptionManager::registerOption(std::shared_ptr<Option> option) {

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

auto cheats = m_enabledCheats.size();

if (option->isCheating()) {
if (isEnabled(id)) m_enabledCheats.emplace(option->getID(), option);
};

m_options.emplace(std::move(id), option);
log::debug("Registered option {} of category {}", option->getID(), option->getCategory());

if (cheats == 0 && m_enabledCheats.size() > cheats) (void)OptionCheatingEvent().send(true);
};
};

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

bool OptionManager::isCheatEnabled() const noexcept {
return m_enabledCheats.size() > 0;
};

std::vector<std::weak_ptr<Option>> OptionManager::getOptions() const {
std::vector<std::weak_ptr<Option>> out;
out.reserve(m_options.size());
Expand Down Expand Up @@ -212,6 +233,11 @@ bool OptionManager::isViewed(ZStringView id) const {
return getOption(id).viewed;
};

bool OptionManager::isCheating(ZStringView id) const {
if (auto o = getOptionInfo(id).lock()) return o->isCheating();
return false;
};

bool OptionManager::getDefaultToggleState(ZStringView id) const noexcept {
if (auto o = getOptionInfo(id).lock()) return o->getDefaultToggleState();
return false;
Expand All @@ -231,6 +257,11 @@ size_t OptionManager::getDelegateCount(std::string_view id) const noexcept {
return 0;
};

bool OptionManager::shouldBeSafeMode() const noexcept {
if (Mod::get()->getSettingValue<bool>("dyn-safe-mode")) return isCheatEnabled();
return Mod::get()->getSettingValue<bool>("safe-mode");
};

void OptionManager::toggleOption(ZStringView id, bool enable) {
setOption(id, enable, isPinned(id));
};
Expand All @@ -243,10 +274,30 @@ void OptionManager::setOption(ZStringView id, bool enable, bool pin, bool viewed

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

auto save = HorribleOptionSave{enable, pin, viewed};
auto cheats = m_enabledCheats.size();

auto const save = HorribleOptionSave{enable, pin, viewed};

(void)Mod::get()->setSavedValue(id, save);
(void)OptionEvent(id).send(save);

if (auto it = m_enabledCheats.find(id); it != m_enabledCheats.end()) {
if (!enable) m_enabledCheats.erase(it);
} else if (enable) {
if (auto o = getOptionInfo(id).lock()) {
if (o->isCheating()) {
log::debug("Enabled cheat option {}, adding to enabled cheats map", id);
m_enabledCheats.emplace(id, o);
};
};
};

auto cheatsNow = m_enabledCheats.size();

if (isCheating(id) && cheats != cheatsNow) {
if (cheats == 0 && cheatsNow > 0) (void)OptionCheatingEvent().send(true);
if (cheats > 0 && cheatsNow == 0) (void)OptionCheatingEvent().send(false);
};
};

OptionManager* OptionManager::get() noexcept {
Expand Down Expand Up @@ -291,7 +342,8 @@ void OptionManagerV2::registerOption(OptionV2 const& option) {
->setDefaultToggleState(option.state)
->setOnline(option.online)
->setRequiresRestart(option.restart)
->setSupportedPlatforms(option.platforms);
->setSupportedPlatforms(option.platforms)
->setCheating(option.cheating);

om->registerOption(opt);
(void)OptionEvent(opt->getID()).send(om->getOption(opt->getID()));
Expand Down
1 change: 1 addition & 0 deletions src/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ namespace horrible {
// For convenience
namespace setting {
inline constexpr auto SafeMode = "safe-mode";
inline constexpr auto DynamicSafeMode = "dyn-safe-mode";
inline constexpr auto FloatingBtn = "floating-btn";
};

Expand Down
1 change: 1 addition & 0 deletions src/hooks/Dementia.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("Chance for the player to occasionally randomly teleport a few steps back while playing a level.\n<cl>suggested by imdissapearinghelp</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Medium)
->setCheating(true)
->autoRegister();

class $modify(DementiaPlayerObject, PlayerObject) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/DoubleJump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("Allows your character to double-jump in a level.\n<cl>created by Cheeseworks</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Low)
->setCheating(true)
->autoRegister();

class $modify(DoubleJumpPlayerObject, PlayerObject) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/Gambler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->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>")
->setCategory(category::misc)
->setSillyTier(SillyTier::High)
->setCheating(true)
->autoRegister();

class $modify(GamblerPlayLayer, PlayLayer) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/InverseInput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("You jump while you're not holding the button, and don't jump while you hold the button. In platformer, horizontal movement inputs are switched with each other.\n<cl>suggested by ItsZentry</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Low)
->setCheating(true)
->autoRegister();

class $modify(InverseInputGJBaseGameLayer, GJBaseGameLayer) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/OnIce.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("Makes every surface icy in platformer mode. Slip n' slide!\n<cl>suggested by TimeRed</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Low)
->setCheating(true)
->autoRegister();

class $modify(OnIcePlayerObject, PlayerObject) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/Parry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ static auto const o = Option::create(THIS_ID)
->setSillyTier(SillyTier::None)
->setRequiresRestart(true)
->setSupportedPlatforms({})
->setCheating(true)
->autoRegister();

// static GameObject* s_pendingKiller1 = nullptr;
Expand Down
1 change: 1 addition & 0 deletions src/hooks/Placebo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("A <cc>1%</c> chance that when you start a level, all of your options get toggled to the opposite of their current state.\n<cl>suggested by tmdXD</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::High)
->setCheating(true)
->autoRegister();

void placeboEffect() {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/Sleepy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("Your character will occasionally fall asleep while playing.\n<cl>suggested by this_guy_yt</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Medium)
->setCheating(true)
->autoRegister();

class $modify(SleepyPlayerObject, PlayerObject) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/Sticky.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("When your character lands on an object, it may stay stuck on its surface until you jump again.\n<cl>created by Cheeseworks</c>")
->setCategory(category::misc)
->setSillyTier(SillyTier::Medium)
->setCheating(true)
->autoRegister();

class $modify(StickyPlayerObject, PlayerObject) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/chances/Pause.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("While playing a level, it will randomly pause itself.\n<cl>suggested by DragonixGD</c>")
->setCategory(category::chances)
->setSillyTier(SillyTier::Low)
->setCheating(true)
->autoRegister();

class $modify(PausePlayerObject, PlayLayer) {
Expand Down
2 changes: 2 additions & 0 deletions src/hooks/jumpscares/ForceLevels.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ static auto const oGrief = Option::create(THIS_ID_GRIEF)
->setCategory(category::jumpscares)
->setSillyTier(SillyTier::High)
->setOnline(true)
->setCheating(true)
->autoRegister();

static auto const oCongreg = Option::create(THIS_ID_CONGREG)
Expand All @@ -24,6 +25,7 @@ static auto const oCongreg = Option::create(THIS_ID_CONGREG)
->setCategory(category::jumpscares)
->setSillyTier(SillyTier::High)
->setOnline(true)
->setCheating(true)
->autoRegister();

static bool trySwitchToLevel(PlayLayer* pl, std::shared_ptr<jumpscares::DownloadDelegate> delegate, int chance, int rng, bool useReplay) {
Expand Down
1 change: 1 addition & 0 deletions src/hooks/obstructive/FakeDeath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ static auto const o = Option::create(THIS_ID)
->setDescription("The player's death effect will show without dying.\n<cl>suggested by DragonixGD</c>")
->setCategory(category::obstructive)
->setSillyTier(SillyTier::Low)
->setCheating(true)
->autoRegister();

class $modify(FakeDeathPlayLayer, PlayLayer) {
Expand Down
Loading
Loading