diff --git a/enhancements/drawdistance.patch b/enhancements/drawdistance.patch
new file mode 100644
index 00000000..2ce30c55
--- /dev/null
+++ b/enhancements/drawdistance.patch
@@ -0,0 +1,641 @@
+diff --git a/include/text_options_strings.h.in b/include/text_options_strings.h.in
+index 8c6732e..3ba7cc8 100644
+--- a/include/text_options_strings.h.in
++++ b/include/text_options_strings.h.in
+@@ -56,6 +56,7 @@
+ #define TEXT_OPT_AUTO _("AUTO")
+ #define TEXT_OPT_HUD _("HUD")
+ #define TEXT_OPT_THREEPT _("THREE POINT")
++#define TEXT_OPT_DRAWDIST _("DRAW DISTANCE")
+ #define TEXT_OPT_APPLY _("APPLY")
+ #define TEXT_OPT_RESETWND _("RESET WINDOW")
+
+@@ -120,6 +121,7 @@
+ #define TEXT_OPT_AUTO _("Auto")
+ #define TEXT_OPT_HUD _("HUD")
+ #define TEXT_OPT_THREEPT _("Three-point")
++#define TEXT_OPT_DRAWDIST _("Draw Distance")
+ #define TEXT_OPT_APPLY _("Apply")
+ #define TEXT_OPT_RESETWND _("Reset Window")
+
+diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c
+index f61f2bf..2848e97 100644
+--- a/src/engine/behavior_script.c
++++ b/src/engine/behavior_script.c
+@@ -13,6 +13,9 @@
+ #include "game/object_list_processor.h"
+ #include "graph_node.h"
+ #include "surface_collision.h"
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
+
+ // Macros for retrieving arguments from behavior scripts.
+ #define BHV_CMD_GET_1ST_U8(index) (u8)((gCurBhvCommand[index] >> 24) & 0xFF) // unused
+@@ -999,7 +1002,7 @@ void cur_obj_update(void) {
+ if (!(objFlags & OBJ_FLAG_ACTIVE_FROM_AFAR)) {
+ // If the object has a render distance, check if it should be shown.
+ #ifndef NODRAWINGDISTANCE
+- if (distanceFromMario > gCurrentObject->oDrawingDistance) {
++ if (distanceFromMario > gCurrentObject->oDrawingDistance * configDrawDistance / 100.0f) {
+ // Out of render distance, hide the object.
+ gCurrentObject->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
+ gCurrentObject->activeFlags |= ACTIVE_FLAG_FAR_AWAY;
+diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c
+index ac2ee50..c62ef00 100644
+--- a/src/engine/surface_load.c
++++ b/src/engine/surface_load.c
+@@ -14,6 +14,9 @@
+ #include "game/mario.h"
+ #include "game/object_list_processor.h"
+ #include "surface_load.h"
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
+
+ s32 unused8038BE90;
+
+@@ -786,7 +789,7 @@ void load_object_collision_model(void) {
+ }
+
+ #ifndef NODRAWINGDISTANCE
+- if (marioDist < gCurrentObject->oDrawingDistance) {
++ if (marioDist < gCurrentObject->oDrawingDistance * configDrawDistance / 100.0f) {
+ #endif
+ gCurrentObject->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
+ #ifndef NODRAWINGDISTANCE
+diff --git a/src/game/behaviors/bub.inc.c b/src/game/behaviors/bub.inc.c
+index 7bf7169..f8577d4 100644
+--- a/src/game/behaviors/bub.inc.c
++++ b/src/game/behaviors/bub.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // bub.c.inc
+
+ // NOTE: These first set of functions spawn a school of bub depending on objF4's
+@@ -9,7 +13,7 @@ void bub_spawner_act_0(void) {
+ s32 i;
+ s32 sp18 = o->oBirdChirpChirpUnkF4;
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 1500.0f) {
++ if (o->oDistanceToMario < 15 * configDrawDistance) {
+ #endif
+ for (i = 0; i < sp18; i++)
+ spawn_object(o, MODEL_BUB, bhvBub);
+diff --git a/src/game/behaviors/chain_chomp.inc.c b/src/game/behaviors/chain_chomp.inc.c
+index 9b9c342..4d0af94 100644
+--- a/src/game/behaviors/chain_chomp.inc.c
++++ b/src/game/behaviors/chain_chomp.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+
+ /**
+ * Behavior for bhvChainChomp, bhvChainChompChainPart, bhvWoodenPost, and bhvChainChompGate.
+@@ -54,7 +58,7 @@ static void chain_chomp_act_uninitialized(void) {
+ s32 i;
+
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 3000.0f) {
++ if (o->oDistanceToMario < 30 * configDrawDistance) {
+ #endif
+ segments = mem_pool_alloc(gObjectMemoryPool, 5 * sizeof(struct ChainSegment));
+ if (segments != NULL) {
+@@ -364,7 +368,7 @@ static void chain_chomp_act_move(void) {
+
+ // Unload chain if mario is far enough
+ #ifndef NODRAWINGDISTANCE
+- if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && o->oDistanceToMario > 4000.0f) {
++ if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && o->oDistanceToMario > 40 * configDrawDistance) {
+ o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
+ o->oForwardVel = o->oVelY = 0.0f;
+ } else {
+diff --git a/src/game/behaviors/cloud.inc.c b/src/game/behaviors/cloud.inc.c
+index c1d708b..ec5f7a9 100644
+--- a/src/game/behaviors/cloud.inc.c
++++ b/src/game/behaviors/cloud.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+
+ /**
+ * Behavior for bhvCloud and bhvCloudPart.
+@@ -48,7 +52,7 @@ static void cloud_act_spawn_parts(void) {
+ */
+ static void cloud_act_fwoosh_hidden(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2000.0f) {
++ if (o->oDistanceToMario < 20 * configDrawDistance) {
+ #endif
+ cur_obj_unhide();
+ o->oAction = CLOUD_ACT_SPAWN_PARTS;
+@@ -63,7 +67,7 @@ static void cloud_act_fwoosh_hidden(void) {
+ */
+ static void cloud_fwoosh_update(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 2500.0f) {
++ if (o->oDistanceToMario > 25 * configDrawDistance) {
+ o->oAction = CLOUD_ACT_UNLOAD;
+ } else {
+ #endif
+diff --git a/src/game/behaviors/coin.inc.c b/src/game/behaviors/coin.inc.c
+index 9b7099d..5370f2d 100644
+--- a/src/game/behaviors/coin.inc.c
++++ b/src/game/behaviors/coin.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // coin.c.inc
+
+ struct ObjectHitbox sYellowCoinHitbox = {
+@@ -185,7 +189,7 @@ void bhv_coin_formation_loop(void) {
+ switch (o->oAction) {
+ case 0:
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2000.0f) {
++ if (o->oDistanceToMario < 20 * configDrawDistance) {
+ #endif
+ for (bitIndex = 0; bitIndex < 8; bitIndex++) {
+ if (!(o->oCoinUnkF4 & (1 << bitIndex)))
+@@ -198,7 +202,7 @@ void bhv_coin_formation_loop(void) {
+ break;
+ case 1:
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 2100.0f)
++ if (o->oDistanceToMario > 21 * configDrawDistance)
+ o->oAction++;
+ #endif
+ break;
+diff --git a/src/game/behaviors/enemy_lakitu.inc.c b/src/game/behaviors/enemy_lakitu.inc.c
+index cacd732..72553dc 100644
+--- a/src/game/behaviors/enemy_lakitu.inc.c
++++ b/src/game/behaviors/enemy_lakitu.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+
+ /**
+ * Behavior for bhvEnemyLakitu.
+@@ -25,7 +29,7 @@ static struct ObjectHitbox sEnemyLakituHitbox = {
+ */
+ static void enemy_lakitu_act_uninitialized(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2000.0f) {
++ if (o->oDistanceToMario < 20 * configDrawDistance) {
+ #endif
+ spawn_object_relative_with_scale(CLOUD_BP_LAKITU_CLOUD, 0, 0, 0, 2.0f, o, MODEL_MIST, bhvCloud);
+
+diff --git a/src/game/behaviors/fish.inc.c b/src/game/behaviors/fish.inc.c
+index d169ecf..f957272 100644
+--- a/src/game/behaviors/fish.inc.c
++++ b/src/game/behaviors/fish.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ /**
+ * @file fish.inc.c
+ * Implements behaviour and spawning for fish located in the Secret Aquarium and other levels.
+@@ -43,7 +47,7 @@ void fish_act_spawn(void) {
+ * Fish moves at random with a max-range of 700.0f.
+ */
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < minDistToMario || gCurrLevelNum == LEVEL_SA) {
++ if (o->oDistanceToMario < minDistToMario * configDrawDistance / 100 || gCurrLevelNum == LEVEL_SA) {
+ #endif
+ for (i = 0; i < schoolQuantity; i++) {
+ fishObject = spawn_object(o, model, bhvFish);
+@@ -64,7 +68,7 @@ void fish_act_spawn(void) {
+ void fish_act_respawn(void) {
+ #ifndef NODRAWINGDISTANCE
+ if (gCurrLevelNum != LEVEL_SA) {
+- if (gMarioObject->oPosY - o->oPosY > 2000.0f) {
++ if (gMarioObject->oPosY - o->oPosY > 20 * configDrawDistance) {
+ o->oAction = FISH_ACT_RESPAWN;
+ }
+ }
+diff --git a/src/game/behaviors/goomba.inc.c b/src/game/behaviors/goomba.inc.c
+index bf47dda..6fce18b 100644
+--- a/src/game/behaviors/goomba.inc.c
++++ b/src/game/behaviors/goomba.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+
+ /**
+ * Behavior for bhvGoomba and bhvGoombaTripletSpawner,
+@@ -79,7 +83,7 @@ void bhv_goomba_triplet_spawner_update(void) {
+ // spawn them
+ if (o->oAction == GOOMBA_TRIPLET_SPAWNER_ACT_UNLOADED) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 3000.0f) {
++ if (o->oDistanceToMario < 30 * configDrawDistance) {
+ #endif
+ // The spawner is capable of spawning more than 3 goombas, but this
+ // is not used in the game
+@@ -102,7 +106,7 @@ void bhv_goomba_triplet_spawner_update(void) {
+ o->oAction += 1;
+ #ifndef NODRAWINGDISTANCE
+ }
+- } else if (o->oDistanceToMario > 4000.0f) {
++ } else if (o->oDistanceToMario > 40 * configDrawDistance) {
+ // If mario is too far away, enter the unloaded action. The goombas
+ // will detect this and unload themselves
+ o->oAction = GOOMBA_TRIPLET_SPAWNER_ACT_UNLOADED;
+diff --git a/src/game/behaviors/heave_ho.inc.c b/src/game/behaviors/heave_ho.inc.c
+index 1a24760..1da0b24 100644
+--- a/src/game/behaviors/heave_ho.inc.c
++++ b/src/game/behaviors/heave_ho.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // heave_ho.c.inc
+
+ s16 D_8032F460[][2] = { { 30, 0 }, { 42, 1 }, { 52, 0 }, { 64, 1 }, { 74, 0 },
+@@ -73,7 +77,7 @@ void heave_ho_act_3(void) {
+
+ void heave_ho_act_0(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (find_water_level(o->oPosX, o->oPosZ) < o->oPosY && o->oDistanceToMario < 4000.0f) {
++ if (find_water_level(o->oPosX, o->oPosZ) < o->oPosY && o->oDistanceToMario < 40 * configDrawDistance) {
+ #else
+ if (find_water_level(o->oPosX, o->oPosZ) < (o->oPosY - 50.0f)) {
+ #endif
+diff --git a/src/game/behaviors/king_bobomb.inc.c b/src/game/behaviors/king_bobomb.inc.c
+index 7942b2b..6aec3d1 100644
+--- a/src/game/behaviors/king_bobomb.inc.c
++++ b/src/game/behaviors/king_bobomb.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // king_bobomb.c.inc
+
+ // Copy of geo_update_projectile_pos_from_parent
+@@ -296,7 +300,7 @@ void king_bobomb_move(void) {
+ cur_obj_call_action_function(sKingBobombActions);
+ exec_anim_sound_state(sKingBobombSoundStates);
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 5000.0f)
++ if (o->oDistanceToMario < 50 * configDrawDistance)
+ #endif
+ cur_obj_enable_rendering();
+ #ifndef NODRAWINGDISTANCE
+diff --git a/src/game/behaviors/lll_floating_wood_piece.inc.c b/src/game/behaviors/lll_floating_wood_piece.inc.c
+index 7994e2d..9e28fca 100644
+--- a/src/game/behaviors/lll_floating_wood_piece.inc.c
++++ b/src/game/behaviors/lll_floating_wood_piece.inc.c
+@@ -1,5 +1,9 @@
+ // lll_floating_wood_piece.c.inc
+
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ void bhv_lll_wood_piece_loop(void) {
+ if (o->oTimer == 0)
+ o->oPosY -= 100.0f;
+@@ -15,7 +19,7 @@ void bhv_lll_floating_wood_bridge_loop(void) {
+ switch (o->oAction) {
+ case 0:
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2500.0f) {
++ if (o->oDistanceToMario < 25 * configDrawDistance) {
+ #endif
+ for (i = 1; i < 4; i++) {
+ sp3C = spawn_object_relative(0, (i - 2) * 300, 0, 0, o, MODEL_LLL_WOOD_BRIDGE,
+@@ -29,7 +33,7 @@ void bhv_lll_floating_wood_bridge_loop(void) {
+ break;
+ case 1:
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 2600.0f)
++ if (o->oDistanceToMario > 26 * configDrawDistance)
+ o->oAction = 2;
+ #endif
+ break;
+diff --git a/src/game/behaviors/lll_rotating_hex_flame.inc.c b/src/game/behaviors/lll_rotating_hex_flame.inc.c
+index fc70733..2c6bad0 100644
+--- a/src/game/behaviors/lll_rotating_hex_flame.inc.c
++++ b/src/game/behaviors/lll_rotating_hex_flame.inc.c
+@@ -1,5 +1,9 @@
+ // lll_rotating_hex_flame.c.inc
+
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ void bhv_lll_rotating_hex_flame_loop(void) {
+ f32 sp24 = o->oLllRotatingHexFlameUnkF4;
+ f32 sp20 = o->oLllRotatingHexFlameUnkF8;
+@@ -31,7 +35,7 @@ void fire_bar_spawn_flames(s16 a0) {
+
+ void fire_bar_act_0(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 3000.0f)
++ if (o->oDistanceToMario < 30 * configDrawDistance)
+ #endif
+ o->oAction = 1;
+ }
+@@ -48,7 +52,7 @@ void fire_bar_act_2(void) {
+ o->oAngleVelYaw = -0x100;
+ o->oMoveAngleYaw += o->oAngleVelYaw;
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 3200.0f)
++ if (o->oDistanceToMario > 32 * configDrawDistance)
+ o->oAction = 3;
+ #endif
+ }
+diff --git a/src/game/behaviors/piranha_plant.inc.c b/src/game/behaviors/piranha_plant.inc.c
+index 328f451..f59d27c 100644
+--- a/src/game/behaviors/piranha_plant.inc.c
++++ b/src/game/behaviors/piranha_plant.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ /**
+ * Behavior for bhvPiranhaPlant.
+ * This controls Piranha Plants, which alternate between sleeping, attacking,
+@@ -331,7 +335,7 @@ void bhv_piranha_plant_loop(void) {
+ #ifndef NODRAWINGDISTANCE
+ // In WF, hide all Piranha Plants once high enough up.
+ if (gCurrLevelNum == LEVEL_WF) {
+- if (gMarioObject->oPosY > 3400.0f)
++ if (gMarioObject->oPosY > 34 * configDrawDistance)
+ cur_obj_hide();
+ else
+ cur_obj_unhide();
+diff --git a/src/game/behaviors/pokey.inc.c b/src/game/behaviors/pokey.inc.c
+index cfcc92c..b19db8e 100644
+--- a/src/game/behaviors/pokey.inc.c
++++ b/src/game/behaviors/pokey.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+
+ /**
+ * Behavior for bhvPokey and bhvPokeyBodyPart.
+@@ -152,7 +156,7 @@ static void pokey_act_uninitialized(void) {
+ s16 partModel;
+
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2000.0f) {
++ if (o->oDistanceToMario < 20 * configDrawDistance) {
+ #endif
+ partModel = MODEL_POKEY_HEAD;
+
+@@ -190,7 +194,7 @@ static void pokey_act_wander(void) {
+ if (o->oPokeyNumAliveBodyParts == 0) {
+ obj_mark_for_deletion(o);
+ #ifndef NODRAWINGDISTANCE
+- } else if (o->oDistanceToMario > 2500.0f) {
++ } else if (o->oDistanceToMario > 25 * configDrawDistance) {
+ o->oAction = POKEY_ACT_UNLOAD_PARTS;
+ o->oForwardVel = 0.0f;
+ #endif
+diff --git a/src/game/behaviors/sl_walking_penguin.inc.c b/src/game/behaviors/sl_walking_penguin.inc.c
+index 59428ac..0599a16 100644
+--- a/src/game/behaviors/sl_walking_penguin.inc.c
++++ b/src/game/behaviors/sl_walking_penguin.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // sl_walking_penguin.c.inc
+
+ struct SLWalkingPenguinStep {
+@@ -98,7 +102,7 @@ void bhv_sl_walking_penguin_loop(void) {
+
+ cur_obj_move_standard(-78);
+ #ifndef NODRAWINGDISTANCE
+- if (!cur_obj_hide_if_mario_far_away_y(1000.0f))
++ if (!cur_obj_hide_if_mario_far_away_y(10 * configDrawDistance))
+ #endif
+ play_penguin_walking_sound(PENGUIN_WALK_BIG);
+
+diff --git a/src/game/behaviors/snufit.inc.c b/src/game/behaviors/snufit.inc.c
+index 2acf442..a740e27 100644
+--- a/src/game/behaviors/snufit.inc.c
++++ b/src/game/behaviors/snufit.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ /**
+ * Behavior file for bhvSnufit and bhvSnufitBalls.
+ * Snufits are present in HMC and CotMC, and are the fly guy
+@@ -181,7 +185,7 @@ void bhv_snufit_balls_loop(void) {
+ // If far from Mario or in a different room, despawn.
+ if ((o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM)
+ #ifndef NODRAWINGDISTANCE
+- || (o->oTimer != 0 && o->oDistanceToMario > 1500.0f)
++ || (o->oTimer != 0 && o->oDistanceToMario > 15 * configDrawDistance)
+ #endif
+ ){
+ obj_mark_for_deletion(o);
+diff --git a/src/game/behaviors/triplet_butterfly.inc.c b/src/game/behaviors/triplet_butterfly.inc.c
+index 5f97185..6309b48 100644
+--- a/src/game/behaviors/triplet_butterfly.inc.c
++++ b/src/game/behaviors/triplet_butterfly.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ struct TripletButterflyActivationData {
+ s32 model;
+ const BehaviorScript *behavior;
+@@ -55,7 +59,7 @@ static void triplet_butterfly_act_init(void) {
+
+ static void triplet_butterfly_act_wander(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 1500.0f) {
++ if (o->oDistanceToMario > 15 * configDrawDistance) {
+ obj_mark_for_deletion(o);
+ } else {
+ #endif
+diff --git a/src/game/behaviors/water_bomb_cannon.inc.c b/src/game/behaviors/water_bomb_cannon.inc.c
+index fb82e43..ae242fa 100644
+--- a/src/game/behaviors/water_bomb_cannon.inc.c
++++ b/src/game/behaviors/water_bomb_cannon.inc.c
+@@ -1,5 +1,9 @@
+ // water_bomb_cannon.inc.c
+
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ void bhv_bubble_cannon_barrel_loop(void) {
+ struct Object *val04;
+
+@@ -39,7 +43,7 @@ void bhv_bubble_cannon_barrel_loop(void) {
+
+ void water_bomb_cannon_act_0(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 2000.0f) {
++ if (o->oDistanceToMario < 20 * configDrawDistance) {
+ #endif
+ spawn_object(o, MODEL_CANNON_BARREL, bhvCannonBarrelBubbles);
+ cur_obj_unhide();
+@@ -53,7 +57,7 @@ void water_bomb_cannon_act_0(void) {
+
+ void water_bomb_cannon_act_1(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario > 2500.0f) {
++ if (o->oDistanceToMario > 25 * configDrawDistance) {
+ o->oAction = 2;
+ } else if (o->oBehParams2ndByte == 0) {
+ #else
+diff --git a/src/game/behaviors/whirlpool.inc.c b/src/game/behaviors/whirlpool.inc.c
+index 5aebebd..04f9c6c 100644
+--- a/src/game/behaviors/whirlpool.inc.c
++++ b/src/game/behaviors/whirlpool.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // whirlpool.c.inc
+
+ static struct ObjectHitbox sWhirlpoolHitbox = {
+@@ -36,7 +40,7 @@ void whirpool_orient_graph(void) {
+
+ void bhv_whirlpool_loop(void) {
+ #ifndef NODRAWINGDISTANCE
+- if (o->oDistanceToMario < 5000.0f) {
++ if (o->oDistanceToMario < 50 * configDrawDistance) {
+ #endif
+ o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
+
+diff --git a/src/game/behaviors/whomp.inc.c b/src/game/behaviors/whomp.inc.c
+index 1f3bcb7..34c5a59 100644
+--- a/src/game/behaviors/whomp.inc.c
++++ b/src/game/behaviors/whomp.inc.c
+@@ -1,3 +1,7 @@
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
++
+ // whomp.c.inc
+
+ void whomp_play_sfx_from_pound_animation(void) {
+@@ -250,9 +254,9 @@ void bhv_whomp_loop(void) {
+ // o->oBehParams2ndByte here seems to be a flag
+ // indicating whether this is a normal or king whomp
+ if (o->oBehParams2ndByte != 0)
+- cur_obj_hide_if_mario_far_away_y(2000.0f);
++ cur_obj_hide_if_mario_far_away_y(20 * configDrawDistance);
+ else
+- cur_obj_hide_if_mario_far_away_y(1000.0f);
++ cur_obj_hide_if_mario_far_away_y(10 * configDrawDistance);
+ #endif
+ load_object_collision_model();
+ }
+diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c
+index 601c45f..369853b 100644
+--- a/src/game/obj_behaviors.c
++++ b/src/game/obj_behaviors.c
+@@ -27,6 +27,9 @@
+ #include "obj_behaviors.h"
+ #include "object_helpers.h"
+ #include "object_list_processor.h"
++#ifndef NODRAWINGDISTANCE
++#include "pc/configfile.h"
++#endif
+ #include "rendering_graph_node.h"
+ #include "save_file.h"
+ #include "spawn_object.h"
+@@ -531,7 +534,7 @@ void set_object_visibility(struct Object *obj, s32 dist) {
+ f32 objZ = obj->oPosZ;
+
+ #ifndef NODRAWINGDISTANCE
+- if (is_point_within_radius_of_mario(objX, objY, objZ, dist) == TRUE) {
++ if (is_point_within_radius_of_mario(objX, objY, objZ, dist * configDrawDistance / 100) == TRUE) {
+ #endif
+ obj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
+ #ifndef NODRAWINGDISTANCE
+diff --git a/src/game/options_menu.c b/src/game/options_menu.c
+index 56ebdeb..e6e4ca2 100644
+--- a/src/game/options_menu.c
++++ b/src/game/options_menu.c
+@@ -85,6 +85,7 @@ static const u8 optsVideoStr[][32] = {
+ { TEXT_OPT_AUTO },
+ { TEXT_OPT_HUD },
+ { TEXT_OPT_THREEPT },
++ { TEXT_OPT_DRAWDIST },
+ { TEXT_OPT_APPLY },
+ };
+
+@@ -261,8 +262,11 @@ static struct Option optsVideo[] = {
+ DEF_OPT_TOGGLE( optsVideoStr[5], &configWindow.vsync ),
+ DEF_OPT_CHOICE( optsVideoStr[1], &configFiltering, filterChoices ),
+ DEF_OPT_TOGGLE( optsVideoStr[7], &configHUD ),
++#ifndef NODRAWINGDISTANCE
++ DEF_OPT_SCROLL( optsVideoStr[9], &configDrawDistance, 50, 509, 10 ),
++#endif
+ DEF_OPT_BUTTON( optsVideoStr[4], optvideo_reset_window ),
+- DEF_OPT_BUTTON( optsVideoStr[9], optvideo_apply ),
++ DEF_OPT_BUTTON( optsVideoStr[10], optvideo_apply ),
+ };
+
+ static struct Option optsAudio[] = {
+diff --git a/src/pc/configfile.c b/src/pc/configfile.c
+index 7411d4f..2c7ce62 100644
+--- a/src/pc/configfile.c
++++ b/src/pc/configfile.c
+@@ -90,6 +90,9 @@ bool configCameraMouse = false;
+ #endif
+ bool configSkipIntro = 0;
+ bool configHUD = true;
++#ifndef NODRAWINGDISTANCE
++unsigned int configDrawDistance = 100;
++#endif
+ #ifdef DISCORDRPC
+ bool configDiscordRPC = true;
+ #endif
+@@ -102,6 +105,9 @@ static const struct ConfigOption options[] = {
+ {.name = "window_h", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.h},
+ {.name = "vsync", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.vsync},
+ {.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering},
++ #ifndef NODRAWINGDISTANCE
++ {.name = "drawing_distance", .type = CONFIG_TYPE_UINT, .uintValue = &configDrawDistance},
++ #endif
+ {.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume},
+ {.name = "music_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMusicVolume},
+ {.name = "sfx_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configSfxVolume},
+diff --git a/src/pc/configfile.h b/src/pc/configfile.h
+index b92ae7b..e20abf3 100644
+--- a/src/pc/configfile.h
++++ b/src/pc/configfile.h
+@@ -55,6 +55,9 @@ extern bool configCameraMouse;
+ extern bool configCameraAnalog;
+ #endif
+ extern bool configHUD;
++#ifndef NODRAWINGDISTANCE
++extern unsigned int configDrawDistance;
++#endif
+ extern bool configSkipIntro;
+ #ifdef DISCORDRPC
+ extern bool configDiscordRPC;
diff --git a/enhancements/v11.alpha.beta_dynos.included.patch b/enhancements/v11.alpha.beta_dynos.included.patch
deleted file mode 100644
index dec38bca..00000000
--- a/enhancements/v11.alpha.beta_dynos.included.patch
+++ /dev/null
@@ -1,4958 +0,0 @@
-diff --git a/Makefile b/Makefile
-index b66723bf..86510d14 100644
---- a/Makefile
-+++ b/Makefile
-@@ -354,6 +354,7 @@ SEG_FILES := $(SEGMENT_ELF_FILES) $(ACTOR_ELF_FILES) $(LEVEL_ELF_FILES)
- ##################### Compiler Options #######################
- INCLUDE_CFLAGS := -I include -I $(BUILD_DIR) -I $(BUILD_DIR)/include -I src -I .
- ENDIAN_BITWIDTH := $(BUILD_DIR)/endian-and-bitwidth
-+include Makefile_dynos
-
- # Huge deleted N64 section was here
-
-diff --git a/Makefile_dynos b/Makefile_dynos
-new file mode 100644
-index 00000000..d3c223b6
---- /dev/null
-+++ b/Makefile_dynos
-@@ -0,0 +1,24 @@
-+# ----------------------
-+# Dynamic Options System
-+# ----------------------
-+
-+DYNOS_INPUT_DIR := ./dynos
-+DYNOS_OUTPUT_DIR := $(BUILD_DIR)/$(BASEDIR)
-+DYNOS_COPY_TO_RES := \
-+ mkdir -p $(DYNOS_INPUT_DIR); \
-+ mkdir -p $(DYNOS_OUTPUT_DIR); \
-+ for f in $(DYNOS_INPUT_DIR)/*.txt; do \
-+ [ -f "$$f" ] || continue; \
-+ cp -f $$f $(DYNOS_OUTPUT_DIR)/$$(basename -- $$f); \
-+ done;
-+
-+DYNOS := $(shell $(call DYNOS_COPY_TO_RES))
-+
-+INCLUDE_CFLAGS += -DDYNOS
-+
-+# Render96 v2.0 detection flag
-+ifeq ($(findstring src/text/libs,$(SRC_DIRS)),src/text/libs)
-+INCLUDE_CFLAGS += -DRENDER96_2_0
-+else
-+$(BUILD_DIR)/src/pc/dynamic_options.o: $(BUILD_DIR)/include/text_strings.h
-+endif
-diff --git a/dynos/cheater_menu.txt b/dynos/cheater_menu.txt
-new file mode 100644
-index 00000000..9a0d0957
---- /dev/null
-+++ b/dynos/cheater_menu.txt
-@@ -0,0 +1,31 @@
-+# Dynamic Options System aka DynOS v0.4
-+# By PeachyPeach
-+#
-+# This is a comment
-+# Here are the available commands:
-+# SUBMENU [Name] [Label] [Label2]
-+# TOGGLE [Name] [Label] [ConfigName] [InitialValue]
-+# SCROLL [Name] [Label] [ConfigName] [InitialValue] [Min] [Max] [Step]
-+# CHOICE [Name] [Label] [ConfigName] [InitialValue] [ChoiceStrings...]
-+# BIND [Name] [Label] [ConfigName] [Mask] [DefaultValues]
-+# BUTTON [Name] [Label] [FuncName]
-+# ENDMENU
-+#
-+# Valid Label characters:
-+# 0-9 A-Z a-z
-+# '.,-()&:!%?"~_
-+#
-+
-+
-+SUBMENU "cheater_submenu" "CHEATER" "CHEATER"
-+ TOGGLE "chaos_mode" "CHAOS MODE" "chaos_mode" 0
-+ BIND "time_button" "Time Stop Button" "time_button" 0x0080 0x0008 0x1001 0xFFFF
-+ TOGGLE "no_heavy" "No Hold Heavy" "no_heavy" 0
-+ TOGGLE "haz_walk" "Walk On Hazards" "haz_walk" 0
-+ TOGGLE "swim_any" "Swim Anywhere" "swim_any" 0
-+ TOGGLE "coin_mag" "Coin Magnet" "coin_mag" 0
-+ SUBMENU "drain_submenu" "Drain JRB?" "CHEATER"
-+ TOGGLE "wat_con" "Control Water" "wat_con" 0
-+ SCROLL "wat_lev" "Water Level (9)" "wat_lev" 0 1 20 1
-+ ENDMENU
-+ENDMENU
-diff --git a/include/libc/time.h b/include/libc/time.h
-new file mode 100644
-index 00000000..907fcbee
---- /dev/null
-+++ b/include/libc/time.h
-@@ -0,0 +1,234 @@
-+/* Copyright (C) 1991-2017 Free Software Foundation, Inc.
-+ This file is part of the GNU C Library.
-+ The GNU C Library is free software; you can redistribute it and/or
-+ modify it under the terms of the GNU Lesser General Public
-+ License as published by the Free Software Foundation; either
-+ version 2.1 of the License, or (at your option) any later version.
-+ The GNU C Library is distributed in the hope that it will be useful,
-+ but WITHOUT ANY WARRANTY; without even the implied warranty of
-+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ Lesser General Public License for more details.
-+ You should have received a copy of the GNU Lesser General Public
-+ License along with the GNU C Library; if not, see
-+ . */
-+/*
-+ * ISO C99 Standard: 7.23 Date and time
-+ */
-+#ifndef _TIME_H
-+#define _TIME_H 1
-+#include
-+#define __need_size_t
-+#define __need_NULL
-+#include
-+/* This defines CLOCKS_PER_SEC, which is the number of processor clock
-+ ticks per second, and possibly a number of other constants. */
-+#include
-+/* Many of the typedefs and structs whose official home is this header
-+ may also need to be defined by other headers. */
-+#include
-+#include
-+#include
-+#if defined __USE_POSIX199309 || defined __USE_ISOC11
-+# include
-+#endif
-+#ifdef __USE_POSIX199309
-+# include
-+# include
-+# include
-+struct sigevent;
-+#endif
-+#ifdef __USE_XOPEN2K
-+# ifndef __pid_t_defined
-+typedef __pid_t pid_t;
-+# define __pid_t_defined
-+# endif
-+#endif
-+#ifdef __USE_XOPEN2K8
-+# include
-+#endif
-+#ifdef __USE_ISOC11
-+/* Time base values for timespec_get. */
-+# define TIME_UTC 1
-+#endif
-+__BEGIN_DECLS
-+/* Time used by the program so far (user time + system time).
-+ The result / CLOCKS_PER_SECOND is program time in seconds. */
-+extern clock_t clock (void) __THROW;
-+/* Return the current time and put it in *TIMER if TIMER is not NULL. */
-+extern time_t time (time_t *__timer) __THROW;
-+/* Return the difference between TIME1 and TIME0. */
-+extern double difftime (time_t __time1, time_t __time0)
-+ __THROW __attribute__ ((__const__));
-+/* Return the `time_t' representation of TP and normalize TP. */
-+extern time_t mktime (struct tm *__tp) __THROW;
-+/* Format TP into S according to FORMAT.
-+ Write no more than MAXSIZE characters and return the number
-+ of characters written, or 0 if it would exceed MAXSIZE. */
-+extern size_t strftime (char *__restrict __s, size_t __maxsize,
-+ const char *__restrict __format,
-+ const struct tm *__restrict __tp) __THROW;
-+#ifdef __USE_XOPEN
-+/* Parse S according to FORMAT and store binary time information in TP.
-+ The return value is a pointer to the first unparsed character in S. */
-+extern char *strptime (const char *__restrict __s,
-+ const char *__restrict __fmt, struct tm *__tp)
-+ __THROW;
-+#endif
-+#ifdef __USE_XOPEN2K8
-+/* Similar to the two functions above but take the information from
-+ the provided locale and not the global locale. */
-+extern size_t strftime_l (char *__restrict __s, size_t __maxsize,
-+ const char *__restrict __format,
-+ const struct tm *__restrict __tp,
-+ locale_t __loc) __THROW;
-+#endif
-+#ifdef __USE_GNU
-+extern char *strptime_l (const char *__restrict __s,
-+ const char *__restrict __fmt, struct tm *__tp,
-+ locale_t __loc) __THROW;
-+#endif
-+/* Return the `struct tm' representation of *TIMER
-+ in Universal Coordinated Time (aka Greenwich Mean Time). */
-+extern struct tm *gmtime (const time_t *__timer) __THROW;
-+/* Return the `struct tm' representation
-+ of *TIMER in the local timezone. */
-+extern struct tm *localtime (const time_t *__timer) __THROW;
-+#ifdef __USE_POSIX
-+/* Return the `struct tm' representation of *TIMER in UTC,
-+ using *TP to store the result. */
-+extern struct tm *gmtime_r (const time_t *__restrict __timer,
-+ struct tm *__restrict __tp) __THROW;
-+/* Return the `struct tm' representation of *TIMER in local time,
-+ using *TP to store the result. */
-+extern struct tm *localtime_r (const time_t *__restrict __timer,
-+ struct tm *__restrict __tp) __THROW;
-+#endif /* POSIX */
-+/* Return a string of the form "Day Mon dd hh:mm:ss yyyy\n"
-+ that is the representation of TP in this format. */
-+extern char *asctime (const struct tm *__tp) __THROW;
-+/* Equivalent to `asctime (localtime (timer))'. */
-+extern char *ctime (const time_t *__timer) __THROW;
-+#ifdef __USE_POSIX
-+/* Reentrant versions of the above functions. */
-+/* Return in BUF a string of the form "Day Mon dd hh:mm:ss yyyy\n"
-+ that is the representation of TP in this format. */
-+extern char *asctime_r (const struct tm *__restrict __tp,
-+ char *__restrict __buf) __THROW;
-+/* Equivalent to `asctime_r (localtime_r (timer, *TMP*), buf)'. */
-+extern char *ctime_r (const time_t *__restrict __timer,
-+ char *__restrict __buf) __THROW;
-+#endif /* POSIX */
-+/* Defined in localtime.c. */
-+extern char *__tzname[2]; /* Current timezone names. */
-+extern int __daylight; /* If daylight-saving time is ever in use. */
-+extern long int __timezone; /* Seconds west of UTC. */
-+#ifdef __USE_POSIX
-+/* Same as above. */
-+extern char *tzname[2];
-+/* Set time conversion information from the TZ environment variable.
-+ If TZ is not defined, a locale-dependent default is used. */
-+extern void tzset (void) __THROW;
-+#endif
-+#if defined __USE_MISC || defined __USE_XOPEN
-+extern int daylight;
-+extern long int timezone;
-+#endif
-+#ifdef __USE_MISC
-+/* Set the system time to *WHEN.
-+ This call is restricted to the superuser. */
-+extern int stime (const time_t *__when) __THROW;
-+#endif
-+/* Nonzero if YEAR is a leap year (every 4 years,
-+ except every 100th isn't, and every 400th is). */
-+#define __isleap(year) \
-+ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
-+#ifdef __USE_MISC
-+/* Miscellaneous functions many Unices inherited from the public domain
-+ localtime package. These are included only for compatibility. */
-+/* Like `mktime', but for TP represents Universal Time, not local time. */
-+extern time_t timegm (struct tm *__tp) __THROW;
-+/* Another name for `mktime'. */
-+extern time_t timelocal (struct tm *__tp) __THROW;
-+/* Return the number of days in YEAR. */
-+extern int dysize (int __year) __THROW __attribute__ ((__const__));
-+#endif
-+#ifdef __USE_POSIX199309
-+/* Pause execution for a number of nanoseconds.
-+ This function is a cancellation point and therefore not marked with
-+ __THROW. */
-+extern int nanosleep (const struct timespec *__requested_time,
-+ struct timespec *__remaining);
-+/* Get resolution of clock CLOCK_ID. */
-+extern int clock_getres (clockid_t __clock_id, struct timespec *__res) __THROW;
-+/* Get current value of clock CLOCK_ID and store it in TP. */
-+extern int clock_gettime (clockid_t __clock_id, struct timespec *__tp) __THROW;
-+/* Set clock CLOCK_ID to value TP. */
-+extern int clock_settime (clockid_t __clock_id, const struct timespec *__tp)
-+ __THROW;
-+# ifdef __USE_XOPEN2K
-+/* High-resolution sleep with the specified clock.
-+ This function is a cancellation point and therefore not marked with
-+ __THROW. */
-+extern int clock_nanosleep (clockid_t __clock_id, int __flags,
-+ const struct timespec *__req,
-+ struct timespec *__rem);
-+/* Return clock ID for CPU-time clock. */
-+extern int clock_getcpuclockid (pid_t __pid, clockid_t *__clock_id) __THROW;
-+# endif
-+/* Create new per-process timer using CLOCK_ID. */
-+extern int timer_create (clockid_t __clock_id,
-+ struct sigevent *__restrict __evp,
-+ timer_t *__restrict __timerid) __THROW;
-+/* Delete timer TIMERID. */
-+extern int timer_delete (timer_t __timerid) __THROW;
-+/* Set timer TIMERID to VALUE, returning old value in OVALUE. */
-+extern int timer_settime (timer_t __timerid, int __flags,
-+ const struct itimerspec *__restrict __value,
-+ struct itimerspec *__restrict __ovalue) __THROW;
-+/* Get current value of timer TIMERID and store it in VALUE. */
-+extern int timer_gettime (timer_t __timerid, struct itimerspec *__value)
-+ __THROW;
-+/* Get expiration overrun for timer TIMERID. */
-+extern int timer_getoverrun (timer_t __timerid) __THROW;
-+#endif
-+#ifdef __USE_ISOC11
-+/* Set TS to calendar time based in time base BASE. */
-+extern int timespec_get (struct timespec *__ts, int __base)
-+ __THROW __nonnull ((1));
-+#endif
-+#ifdef __USE_XOPEN_EXTENDED
-+/* Set to one of the following values to indicate an error.
-+ 1 the DATEMSK environment variable is null or undefined,
-+ 2 the template file cannot be opened for reading,
-+ 3 failed to get file status information,
-+ 4 the template file is not a regular file,
-+ 5 an error is encountered while reading the template file,
-+ 6 memory allication failed (not enough memory available),
-+ 7 there is no line in the template that matches the input,
-+ 8 invalid input specification Example: February 31 or a time is
-+ specified that can not be represented in a time_t (representing
-+ the time in seconds since 00:00:00 UTC, January 1, 1970) */
-+extern int getdate_err;
-+/* Parse the given string as a date specification and return a value
-+ representing the value. The templates from the file identified by
-+ the environment variable DATEMSK are used. In case of an error
-+ `getdate_err' is set.
-+ This function is a possible cancellation point and therefore not
-+ marked with __THROW. */
-+extern struct tm *getdate (const char *__string);
-+#endif
-+#ifdef __USE_GNU
-+/* Since `getdate' is not reentrant because of the use of `getdate_err'
-+ and the static buffer to return the result in, we provide a thread-safe
-+ variant. The functionality is the same. The result is returned in
-+ the buffer pointed to by RESBUFP and in case of an error the return
-+ value is != 0 with the same values as given above for `getdate_err'.
-+ This function is not part of POSIX and therefore no official
-+ cancellation point. But due to similarity with an POSIX interface
-+ or due to the implementation it is a cancellation point and
-+ therefore not marked with __THROW. */
-+extern int getdate_r (const char *__restrict __string,
-+ struct tm *__restrict __resbufp);
-+#endif
-+__END_DECLS
-+#endif /* time.h. */
-\ No newline at end of file
-diff --git a/levels/bowser_1/script.c b/levels/bowser_1/script.c
-index e70de999..10da1481 100644
---- a/levels/bowser_1/script.c
-+++ b/levels/bowser_1/script.c
-@@ -22,8 +22,11 @@ const LevelScript level_bowser_1_entry[] = {
- LOAD_MIO0(/*seg*/ 0x0A, _bidw_skybox_mio0SegmentRomStart, _bidw_skybox_mio0SegmentRomEnd),
- LOAD_MIO0(/*seg*/ 0x06, _group12_mio0SegmentRomStart, _group12_mio0SegmentRomEnd),
- LOAD_RAW( /*seg*/ 0x0D, _group12_geoSegmentRomStart, _group12_geoSegmentRomEnd),
-+ LOAD_MIO0(/*seg*/ 0x08, _common0_mio0SegmentRomStart, _common0_mio0SegmentRomEnd),
-+ LOAD_RAW(/*seg*/ 0x0F, _common0_geoSegmentRomStart, _common0_geoSegmentRomEnd),
- ALLOC_LEVEL_POOL(),
- MARIO(/*model*/ MODEL_MARIO, /*behParam*/ 0x00000001, /*beh*/ bhvMario),
-+ JUMP_LINK(script_func_global_1),
- JUMP_LINK(script_func_global_13),
- LOAD_MODEL_FROM_GEO(MODEL_LEVEL_GEOMETRY_03, bowser_1_yellow_sphere_geo),
-
-diff --git a/levels/bowser_2/script.c b/levels/bowser_2/script.c
-index b848c00c..984dfded 100644
---- a/levels/bowser_2/script.c
-+++ b/levels/bowser_2/script.c
-@@ -32,8 +32,11 @@ const LevelScript level_bowser_2_entry[] = {
- LOAD_MIO0( /*seg*/ 0x07, _bowser_2_segment_7SegmentRomStart, _bowser_2_segment_7SegmentRomEnd),
- LOAD_MIO0( /*seg*/ 0x06, _group12_mio0SegmentRomStart, _group12_mio0SegmentRomEnd),
- LOAD_RAW( /*seg*/ 0x0D, _group12_geoSegmentRomStart, _group12_geoSegmentRomEnd),
-+ LOAD_MIO0( /*seg*/ 0x08, _common0_mio0SegmentRomStart, _common0_mio0SegmentRomEnd),
-+ LOAD_RAW( /*seg*/ 0x0F, _common0_geoSegmentRomStart, _common0_geoSegmentRomEnd),
- ALLOC_LEVEL_POOL(),
- MARIO(/*model*/ MODEL_MARIO, /*behParam*/ 0x00000001, /*beh*/ bhvMario),
-+ JUMP_LINK(script_func_global_1),
- JUMP_LINK(script_func_global_13),
- LOAD_MODEL_FROM_GEO(MODEL_BOWSER_2_TILTING_ARENA, bowser_2_geo_000170),
-
-diff --git a/levels/bowser_3/script.c b/levels/bowser_3/script.c
-index 756ef81b..c87e6a5c 100644
---- a/levels/bowser_3/script.c
-+++ b/levels/bowser_3/script.c
-@@ -40,8 +40,11 @@ const LevelScript level_bowser_3_entry[] = {
- LOAD_MIO0(/*seg*/ 0x06, _group12_mio0SegmentRomStart, _group12_mio0SegmentRomEnd),
- LOAD_RAW( /*seg*/ 0x0D, _group12_geoSegmentRomStart, _group12_geoSegmentRomEnd),
- LOAD_MIO0(/*seg*/ 0x0A, _bits_skybox_mio0SegmentRomStart, _bits_skybox_mio0SegmentRomEnd),
-+ LOAD_MIO0(/*seg*/ 0x08, _common0_mio0SegmentRomStart, _common0_mio0SegmentRomEnd),
-+ LOAD_RAW(/*seg*/ 0x0F, _common0_geoSegmentRomStart, _common0_geoSegmentRomEnd),
- ALLOC_LEVEL_POOL(),
- MARIO(/*model*/ MODEL_MARIO, /*behParam*/ 0x00000001, /*beh*/ bhvMario),
-+ JUMP_LINK(script_func_global_1),
- JUMP_LINK(script_func_global_13),
- LOAD_MODEL_FROM_GEO(MODEL_BOWSER_3_FALLING_PLATFORM_1, bowser_3_geo_000290),
- LOAD_MODEL_FROM_GEO(MODEL_BOWSER_3_FALLING_PLATFORM_2, bowser_3_geo_0002A8),
-diff --git a/levels/castle_inside/script.c b/levels/castle_inside/script.c
-index 5d9ae4fb..1792886e 100644
---- a/levels/castle_inside/script.c
-+++ b/levels/castle_inside/script.c
-@@ -225,8 +225,11 @@ const LevelScript level_castle_inside_entry[] = {
- LOAD_MIO0_TEXTURE(/*seg*/ 0x09, _inside_mio0SegmentRomStart, _inside_mio0SegmentRomEnd),
- LOAD_MIO0( /*seg*/ 0x06, _group15_mio0SegmentRomStart, _group15_mio0SegmentRomEnd),
- LOAD_RAW( /*seg*/ 0x0D, _group15_geoSegmentRomStart, _group15_geoSegmentRomEnd),
-+ LOAD_MIO0( /*seg*/ 0x08, _common0_mio0SegmentRomStart, _common0_mio0SegmentRomEnd),
-+ LOAD_RAW( /*seg*/ 0x0F, _common0_geoSegmentRomStart, _common0_geoSegmentRomEnd),
- ALLOC_LEVEL_POOL(),
- MARIO(/*model*/ MODEL_MARIO, /*behParam*/ 0x00000001, /*beh*/ bhvMario),
-+ JUMP_LINK(script_func_global_1),
- JUMP_LINK(script_func_global_16),
- LOAD_MODEL_FROM_GEO(MODEL_CASTLE_BOWSER_TRAP, castle_geo_000F18),
- LOAD_MODEL_FROM_GEO(MODEL_CASTLE_WATER_LEVEL_PILLAR, castle_geo_001940),
-diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c
-index 5b6775fe..d8f568dc 100644
---- a/src/engine/surface_collision.c
-+++ b/src/engine/surface_collision.c
-@@ -5,6 +5,7 @@
- #include "game/level_update.h"
- #include "game/mario.h"
- #include "game/object_list_processor.h"
-+#include "pc/cheats.h"
- #include "surface_collision.h"
- #include "surface_load.h"
- #include "math_util.h"
-@@ -40,6 +41,11 @@ static s32 find_wall_collisions_from_list(struct SurfaceNode *surfaceNode,
- surf = surfaceNode->surface;
- surfaceNode = surfaceNode->next;
-
-+ /*No Clip Cheats*/
-+ if (Cheats.EnableCheats && Cheats.NoBounds) {
-+ continue;
-+ }
-+
- // Exclude a large number of walls immediately to optimize.
- if (y < surf->lowerY || y > surf->upperY) {
- continue;
-@@ -170,7 +176,12 @@ s32 f32_find_wall_collision(f32 *xPtr, f32 *yPtr, f32 *zPtr, f32 offsetY, f32 ra
-
- collision.numWalls = 0;
-
-- numCollisions = find_wall_collisions(&collision);
-+ /*No Clip Cheats*/
-+ if (Cheats.EnableCheats && Cheats.NoBounds) {
-+ numCollisions = 0;
-+ } else {
-+ numCollisions = find_wall_collisions(&collision);
-+ }
-
- *xPtr = collision.x;
- *yPtr = collision.y;
-@@ -191,6 +202,10 @@ s32 find_wall_collisions(struct WallCollisionData *colData) {
-
- colData->numWalls = 0;
-
-+ if (Cheats.EnableCheats && Cheats.NoBounds) {
-+ return numCollisions;
-+ }
-+
- if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
- return numCollisions;
- }
-@@ -236,6 +251,10 @@ static struct Surface *find_ceil_from_list(struct SurfaceNode *surfaceNode, s32
- surf = surfaceNode->surface;
- surfaceNode = surfaceNode->next;
-
-+ if (Cheats.EnableCheats && Cheats.NoBounds) {
-+ continue;
-+ }
-+
- x1 = surf->vertex1[0];
- z1 = surf->vertex1[2];
- z2 = surf->vertex2[2];
-@@ -320,6 +339,10 @@ f32 find_ceil(f32 posX, f32 posY, f32 posZ, struct Surface **pceil) {
- z = (s16) posZ;
- *pceil = NULL;
-
-+ if (Cheats.EnableCheats && Cheats.NoBounds) {
-+ return height;
-+ }
-+
- if (x <= -LEVEL_BOUNDARY_MAX || x >= LEVEL_BOUNDARY_MAX) {
- return height;
- }
-diff --git a/src/game/cheats_menu.h b/src/game/cheats_menu.h
-new file mode 100644
-index 00000000..93bb7be7
---- /dev/null
-+++ b/src/game/cheats_menu.h
-@@ -0,0 +1,217 @@
-+#ifndef CHEATS_MENU_H
-+#define CHEATS_MENU_H
-+
-+#include "text/text-loader.h"
-+
-+static const u8 optsCoinCheatStr[][32] = {
-+ "TEXT_OPT_COIN1",
-+ "TEXT_OPT_COIN2",
-+ "TEXT_OPT_COIN3",
-+ "TEXT_OPT_COIN4",
-+};
-+
-+static const u8 *CoinChoices[] = {
-+ optsCoinCheatStr[0],
-+ optsCoinCheatStr[1],
-+ optsCoinCheatStr[2],
-+ optsCoinCheatStr[3],
-+};
-+
-+static const u8 optsSeqStr[][64] = {
-+ "TEXT_OPT_SEQ1",
-+ "TEXT_OPT_SEQ2",
-+ "TEXT_OPT_SEQ3",
-+ "TEXT_OPT_SEQ4",
-+ "TEXT_OPT_SEQ5",
-+ "TEXT_OPT_SEQ6",
-+ "TEXT_OPT_SEQ7",
-+ "TEXT_OPT_SEQ8",
-+ "TEXT_OPT_SEQ9",
-+ "TEXT_OPT_SEQ10",
-+ "TEXT_OPT_SEQ11",
-+ "TEXT_OPT_SEQ12",
-+ "TEXT_OPT_SEQ13",
-+ "TEXT_OPT_SEQ14",
-+ "TEXT_OPT_SEQ15",
-+ "TEXT_OPT_SEQ16",
-+ "TEXT_OPT_SEQ17",
-+ "TEXT_OPT_SEQ18",
-+ "TEXT_OPT_SEQ19",
-+};
-+
-+static const u8 *SeqChoices[] = {
-+ optsSeqStr[0],
-+ optsSeqStr[1],
-+ optsSeqStr[2],
-+ optsSeqStr[3],
-+ optsSeqStr[4],
-+ optsSeqStr[5],
-+ optsSeqStr[6],
-+ optsSeqStr[7],
-+ optsSeqStr[8],
-+ optsSeqStr[9],
-+ optsSeqStr[10],
-+ optsSeqStr[11],
-+ optsSeqStr[12],
-+ optsSeqStr[13],
-+ optsSeqStr[14],
-+ optsSeqStr[15],
-+ optsSeqStr[16],
-+ optsSeqStr[17],
-+ optsSeqStr[18],
-+};
-+
-+static const u8 optsSpeedStr[][16] = {
-+ "TEXT_OPT_SS1",
-+ "TEXT_OPT_SS2",
-+ "TEXT_OPT_SS3",
-+ "TEXT_OPT_SS4",
-+ "TEXT_OPT_SS5",
-+};
-+
-+static const u8 *SpeedChoices[] = {
-+ optsSpeedStr[0],
-+ optsSpeedStr[1],
-+ optsSpeedStr[2],
-+ optsSpeedStr[3],
-+ optsSpeedStr[4],
-+};
-+
-+static const u8 optsPlayAsCheatStr[][32] = {
-+ "TEXT_OPT_PA1",
-+ "TEXT_OPT_PA2",
-+ "TEXT_OPT_PA3",
-+ "TEXT_OPT_PA4",
-+ "TEXT_OPT_PA5",
-+ "TEXT_OPT_PA6",
-+ "TEXT_OPT_PA7",
-+ "TEXT_OPT_PA8",
-+};
-+
-+static const u8* PlayAsCheatChoices[] = {
-+ optsPlayAsCheatStr[0],
-+ optsPlayAsCheatStr[1],
-+ optsPlayAsCheatStr[2],
-+ optsPlayAsCheatStr[3],
-+ optsPlayAsCheatStr[4],
-+ optsPlayAsCheatStr[5],
-+ optsPlayAsCheatStr[6],
-+ optsPlayAsCheatStr[7],
-+};
-+
-+
-+static const u8 optsHurtCheatStr[][32] = {
-+ "TEXT_OPT_HURTCHT1",
-+ "TEXT_OPT_HURTCHT2",
-+ "TEXT_OPT_HURTCHT3",
-+ "TEXT_OPT_HURTCHT4",
-+};
-+
-+static const u8* HurtCheatChoices[] = {
-+ optsHurtCheatStr[0],
-+ optsHurtCheatStr[1],
-+ optsHurtCheatStr[2],
-+ optsHurtCheatStr[3],
-+};
-+
-+static const u8 optsSpamCheatStr[][32] = {
-+ "TEXT_OPT_SPAMCHT1",
-+ "TEXT_OPT_SPAMCHT2",
-+ "TEXT_OPT_SPAMCHT3",
-+ "TEXT_OPT_SPAMCHT4",
-+ "TEXT_OPT_SPAMCHT5",
-+ "TEXT_OPT_SPAMCHT6",
-+ "TEXT_OPT_SPAMCHT7",
-+ "TEXT_OPT_SPAMCHT8",
-+ "TEXT_OPT_SPAMCHT9",
-+ "TEXT_OPT_SPAMCHT10",
-+ "TEXT_OPT_SPAMCHT11",
-+ "TEXT_OPT_SPAMCHT12",
-+ "TEXT_OPT_SPAMCHT13",
-+ "TEXT_OPT_SPAMCHT14",
-+};
-+
-+static const u8* SpamCheatChoices[] = {
-+ optsSpamCheatStr[0],
-+ optsSpamCheatStr[1],
-+ optsSpamCheatStr[2],
-+ optsSpamCheatStr[3],
-+ optsSpamCheatStr[4],
-+ optsSpamCheatStr[5],
-+ optsSpamCheatStr[6],
-+ optsSpamCheatStr[7],
-+ optsSpamCheatStr[8],
-+ optsSpamCheatStr[9],
-+ optsSpamCheatStr[10],
-+ optsSpamCheatStr[11],
-+ optsSpamCheatStr[12],
-+ optsSpamCheatStr[13],
-+};
-+
-+static const u8 optsBLJCheatStr[][32] = {
-+ "TEXT_OPT_BLJCHT1",
-+ "TEXT_OPT_BLJCHT2",
-+ "TEXT_OPT_BLJCHT3",
-+ "TEXT_OPT_BLJCHT4",
-+ "TEXT_OPT_BLJCHT5",
-+ "TEXT_OPT_BLJCHT6",
-+ "TEXT_OPT_BLJCHT7",
-+ "TEXT_OPT_BLJCHT8",
-+ "TEXT_OPT_BLJCHT9",
-+ "TEXT_OPT_BLJCHT10",
-+ "TEXT_OPT_BLJCHT11",
-+ "TEXT_OPT_BLJCHT12",
-+ "TEXT_OPT_BLJCHT13",
-+};
-+
-+static const u8* bljCheatChoices[] = {
-+ optsBLJCheatStr[0],
-+ optsBLJCheatStr[1],
-+ optsBLJCheatStr[2],
-+ optsBLJCheatStr[3],
-+ optsBLJCheatStr[4],
-+ optsBLJCheatStr[5],
-+ optsBLJCheatStr[6],
-+ optsBLJCheatStr[7],
-+ optsBLJCheatStr[8],
-+ optsBLJCheatStr[9],
-+ optsBLJCheatStr[10],
-+ optsBLJCheatStr[11],
-+ optsBLJCheatStr[12],
-+};
-+
-+static const u8 optsCheatsStr2[][64] = {
-+ "TEXT_OPT_COIN",
-+ "TEXT_OPT_HOVER",
-+ "TEXT_OPT_MOON",
-+ "TEXT_OPT_RUN",
-+ "TEXT_OPT_NDB",
-+ "TEXT_OPT_JUMP",
-+ "TEXT_OPT_SPDDPS",
-+ "TEXT_OPT_TPF",
-+ "TEXT_OPT_JB",
-+ "TEXT_OPT_JBC",
-+ "TEXT_OPT_QUIKEND",
-+ "TEXT_OPT_HURT",
-+ "TEXT_OPT_CANN",
-+ "TEXT_OPT_AWK",
-+ "TEXT_OPT_SHELL",
-+ "TEXT_OPT_BOB",
-+ "TEXT_OPT_SPAMBA",
-+ "TEXT_OPT_SWIM",
-+ "TEXT_OPT_WING_CAP",
-+ "TEXT_OPT_METAL_CAP",
-+ "TEXT_OPT_VANISH_CAP",
-+ "TEXT_OPT_REMOVE_CAP",
-+ "TEXT_OPT_NORMAL_CAP",
-+ "TEXT_OPT_BLJ",
-+ "TEXT_OPT_PAC",
-+ "TEXT_OPT_TRIPLE",
-+ "TEXT_OPT_FLY",
-+ "TEXT_OPT_NOB",
-+ "TEXT_OPT_FLJ",
-+ "TEXT_OPT_TS",
-+
-+};
-+
-+#endif // CHEATS_MENU_H
-diff --git a/src/game/game_init.c b/src/game/game_init.c
-index c2df4510..710fafb6 100644
---- a/src/game/game_init.c
-+++ b/src/game/game_init.c
-@@ -384,9 +384,6 @@ void adjust_analog_stick(struct Controller *controller) {
- // if a demo sequence exists, this will run the demo
- // input list until it is complete. called every frame.
- void run_demo_inputs(void) {
-- // eliminate the unused bits.
-- gControllers[0].controllerData->button &= VALID_BUTTONS;
--
- /*
- Check if a demo inputs list
- exists and if so, run the
-diff --git a/src/game/interaction.c b/src/game/interaction.c
-index 7a9c5203..ed931b37 100644
---- a/src/game/interaction.c
-+++ b/src/game/interaction.c
-@@ -1,5 +1,6 @@
- #include
-
-+#include "pc/cheats.h"
- #include "area.h"
- #include "actors/common1.h"
- #include "audio/external.h"
-@@ -14,6 +15,7 @@
- #include "interaction.h"
- #include "level_update.h"
- #include "mario.h"
-+#include "mario_cheats.h"
- #include "mario_step.h"
- #include "memory.h"
- #include "obj_behaviors.h"
-@@ -1777,21 +1779,26 @@ void mario_process_interactions(struct MarioState *m) {
- }
-
- void check_death_barrier(struct MarioState *m) {
-- if (m->pos[1] < m->floorHeight + 2048.0f) {
-- if (level_trigger_warp(m, WARP_OP_WARP_FLOOR) == 20 && !(m->flags & MARIO_UNKNOWN_18)) {
-- play_sound(SOUND_MARIO_WAAAOOOW, m->marioObj->header.gfx.cameraToObject);
-+ while (Cheats.NDB == false) {
-+ if (m->pos[1] < m->floorHeight + 2048.0f) {
-+ if (level_trigger_warp(m, WARP_OP_WARP_FLOOR) == 20 && !(m->flags & MARIO_UNKNOWN_18)) {
-+ play_sound(SOUND_MARIO_WAAAOOOW, m->marioObj->header.gfx.cameraToObject);
-+ }
- }
-+ break;
- }
- }
-
- void check_lava_boost(struct MarioState *m) {
-- if (!(m->action & ACT_FLAG_RIDING_SHELL) && m->pos[1] < m->floorHeight + 10.0f) {
-- if (!(m->flags & MARIO_METAL_CAP)) {
-- m->hurtCounter += (m->flags & MARIO_CAP_ON_HEAD) ? 12 : 18;
-- }
-+ if (!(Cheats.EnableCheats && HAZ_WALK == 1)) {
-+ if (!(m->action & ACT_FLAG_RIDING_SHELL) && m->pos[1] < m->floorHeight + 10.0f) {
-+ if (!(m->flags & MARIO_METAL_CAP)) {
-+ m->hurtCounter += (m->flags & MARIO_CAP_ON_HEAD) ? 12 : 18;
-+ }
-
-- update_mario_sound_and_camera(m);
-- drop_and_set_mario_action(m, ACT_LAVA_BOOST, 0);
-+ update_mario_sound_and_camera(m);
-+ drop_and_set_mario_action(m, ACT_LAVA_BOOST, 0);
-+ }
- }
- }
-
-@@ -1845,6 +1852,9 @@ void mario_handle_special_floors(struct MarioState *m) {
- if (!(m->action & ACT_FLAG_AIR) && !(m->action & ACT_FLAG_SWIMMING)) {
- switch (floorType) {
- case SURFACE_BURNING:
-+ if (Cheats.EnableCheats && HAZ_WALK == 1) {
-+ break;
-+ }
- check_lava_boost(m);
- break;
- }
-diff --git a/src/game/mario.c b/src/game/mario.c
-index 5f8e5114..2b231917 100644
---- a/src/game/mario.c
-+++ b/src/game/mario.c
-@@ -1,5 +1,6 @@
- #include
-
-+#include "mario_cheats.h"
- #include "sm64.h"
- #include "area.h"
- #include "audio/data.h"
-@@ -877,7 +878,7 @@ static u32 set_mario_action_airborne(struct MarioState *m, u32 action, u32 actio
-
- //! (BLJ's) This properly handles long jumps from getting forward speed with
- // too much velocity, but misses backwards longs allowing high negative speeds.
-- if ((m->forwardVel *= 1.5f) > 48.0f) {
-+ if ((m->forwardVel *= 1.5f) > 48.0f && !(Cheats.EnableCheats && Cheats.FLJ)) {
- m->forwardVel = 48.0f;
- }
- break;
-@@ -1223,6 +1224,10 @@ void squish_mario_model(struct MarioState *m) {
- }
- else if (Cheats.TinyMario) {
- vec3f_set(m->marioObj->header.gfx.scale, 0.2f, 0.2f, 0.2f);
-+ } else if (Cheats.PAC == 3) {
-+ vec3f_set(m->marioObj->header.gfx.scale, 1.5f, 1.5f, 1.5f);
-+ } else if (Cheats.PAC == 5) {
-+ vec3f_set(m->marioObj->header.gfx.scale, 1.5f, 1.5f, 1.5f);
- }
- else {
- vec3f_set(m->marioObj->header.gfx.scale, 1.0f, 1.0f, 1.0f);
-@@ -1413,7 +1418,9 @@ void update_mario_inputs(struct MarioState *m) {
- update_mario_geometry_inputs(m);
-
- debug_print_speed_action_normal(m);
--
-+
-+ cheats_mario_inputs(m);
-+
- /* Moonjump cheat */
- while (Cheats.MoonJump == true && Cheats.EnableCheats == true && m->controller->buttonDown & L_TRIG ){
- m->vel[1] = 25;
-@@ -1688,6 +1695,8 @@ void mario_update_hitbox_and_cap_model(struct MarioState *m) {
- // Short hitbox for crouching/crawling/etc.
- if (m->action & ACT_FLAG_SHORT_HITBOX) {
- m->marioObj->hitboxHeight = 100.0f;
-+ } else if (Cheats.EnableCheats && Cheats.PAC > 0) {
-+ m->marioObj->hitboxHeight = 120.0f;
- } else {
- m->marioObj->hitboxHeight = 160.0f;
- }
-diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c
-index 2c9db90b..cabedfa4 100644
---- a/src/game/mario_actions_airborne.c
-+++ b/src/game/mario_actions_airborne.c
-@@ -1,5 +1,7 @@
- #include
-
-+#include "mario_cheats.h"
-+#include "pc/cheats.h"
- #include "sm64.h"
- #include "area.h"
- #include "audio/data.h"
-@@ -332,8 +334,15 @@ void update_flying(struct MarioState *m) {
- update_flying_pitch(m);
- update_flying_yaw(m);
-
-- m->forwardVel -= 2.0f * ((f32) m->faceAngle[0] / 0x4000) + 0.1f;
-+ /*Flyer Cheat*/
-+ if (Cheats.Fly) {
-+ if (m->forwardVel < 30.0f) {
-+ m->forwardVel += 2.0f;
-+ }
-+ }
- m->forwardVel -= 0.5f * (1.0f - coss(m->angleVel[1]));
-+ m->forwardVel -= 2.0f * ((f32) m->faceAngle[0] / 0x4000) + 0.1f;
-+
-
- if (m->forwardVel < 0.0f) {
- m->forwardVel = 0.0f;
-@@ -372,6 +381,8 @@ u32 common_air_action_step(struct MarioState *m, u32 landAction, s32 animation,
- stepResult = perform_air_step(m, stepArg);
- switch (stepResult) {
- case AIR_STEP_NONE:
-+ // BLJ anywhere cheat
-+ cheats_air_step(m);
- set_mario_animation(m, animation);
- break;
-
-@@ -909,7 +920,13 @@ s32 act_ground_pound(struct MarioState *m) {
- if (m->actionState == 0) {
- if (m->actionTimer < 10) {
- yOffset = 20 - 2 * m->actionTimer;
-- if (m->pos[1] + yOffset + 160.0f < m->ceilHeight) {
-+ if (Cheats.EnableCheats && Cheats.PAC > 0) {
-+ if (m->pos[1] + yOffset + 120.0f < m->ceilHeight) {
-+ m->pos[1] += yOffset;
-+ m->peakHeight = m->pos[1];
-+ vec3f_copy(m->marioObj->header.gfx.pos, m->pos);
-+ }
-+ } else if (m->pos[1] + yOffset + 160.0f < m->ceilHeight) {
- m->pos[1] += yOffset;
- m->peakHeight = m->pos[1];
- vec3f_copy(m->marioObj->header.gfx.pos, m->pos);
-diff --git a/src/game/mario_actions_automatic.c b/src/game/mario_actions_automatic.c
-index a74f4a18..c9ec20e5 100644
---- a/src/game/mario_actions_automatic.c
-+++ b/src/game/mario_actions_automatic.c
-@@ -1,6 +1,7 @@
- #include
-
- #include "sm64.h"
-+#include "pc/cheats.h"
- #include "behavior_data.h"
- #include "mario_actions_automatic.h"
- #include "audio/external.h"
-@@ -80,7 +81,12 @@ s32 set_pole_position(struct MarioState *m, f32 offsetY) {
- collided |= f32_find_wall_collision(&m->pos[0], &m->pos[1], &m->pos[2], 30.0f, 24.0f);
-
- ceilHeight = vec3f_find_ceil(m->pos, m->pos[1], &ceil);
-- if (m->pos[1] > ceilHeight - 160.0f) {
-+ if (Cheats.EnableCheats && Cheats.PAC > 0) {
-+ if (m->pos[1] > ceilHeight - 120.0f) {
-+ m->pos[1] = ceilHeight - 120.0f;
-+ marioObj->oMarioPolePos = m->pos[1] - m->usedObj->oPosY;
-+ }
-+ } else if (m->pos[1] > ceilHeight - 160.0f) {
- m->pos[1] = ceilHeight - 160.0f;
- marioObj->oMarioPolePos = m->pos[1] - m->usedObj->oPosY;
- }
-diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c
-index 72e76929..5c14f8e5 100644
---- a/src/game/mario_actions_cutscene.c
-+++ b/src/game/mario_actions_cutscene.c
-@@ -551,18 +551,18 @@ s32 act_debug_free_move(struct MarioState *m) {
- u32 action;
-
- // integer immediates, generates convert instructions for some reason
-- speed = gPlayer1Controller->buttonDown & B_BUTTON ? 4 : 1;
-- if (gPlayer1Controller->buttonDown & L_TRIG) {
-+ speed = gPlayer1Controller->buttonDown & L_TRIG ? 4 : 1;
-+ if (gPlayer1Controller->buttonDown & R_TRIG) {
- speed = 0.01f;
- }
-
-- set_mario_animation(m, MARIO_ANIM_A_POSE);
-+ set_mario_animation(m, MARIO_ANIM_BREAKDANCE);
- vec3f_copy(pos, m->pos);
-
-- if (gPlayer1Controller->buttonDown & U_JPAD) {
-+ if (gPlayer1Controller->buttonDown & A_BUTTON) {
- pos[1] += 16.0f * speed;
- }
-- if (gPlayer1Controller->buttonDown & D_JPAD) {
-+ if (gPlayer1Controller->buttonDown & Z_TRIG) {
- pos[1] -= 16.0f * speed;
- }
-
-@@ -585,7 +585,7 @@ s32 act_debug_free_move(struct MarioState *m) {
- vec3f_copy(m->marioObj->header.gfx.pos, m->pos);
- vec3s_set(m->marioObj->header.gfx.angle, 0, m->faceAngle[1], 0);
-
-- if (gPlayer1Controller->buttonPressed == A_BUTTON) {
-+ if (gPlayer1Controller->buttonPressed == B_BUTTON) {
- if (m->pos[1] <= m->waterLevel - 100) {
- action = ACT_WATER_IDLE;
- } else {
-diff --git a/src/game/mario_actions_moving.c b/src/game/mario_actions_moving.c
-index 0ccbccf1..035a88f7 100644
---- a/src/game/mario_actions_moving.c
-+++ b/src/game/mario_actions_moving.c
-@@ -9,6 +9,7 @@
- #include "area.h"
- #include "interaction.h"
- #include "mario_actions_object.h"
-+#include "mario_cheats.h"
- #include "memory.h"
- #include "behavior_data.h"
- #include "thread6.h"
-@@ -1866,6 +1867,7 @@ s32 act_long_jump_land(struct MarioState *m) {
- play_sound_if_no_flag(m, SOUND_MARIO_UH2_2, MARIO_MARIO_SOUND_PLAYED);
- }
-
-+ cheats_long_jump(m);
- common_landing_action(m,
- !m->marioObj->oMarioLongJumpIsSlow ? MARIO_ANIM_CROUCH_FROM_FAST_LONGJUMP
- : MARIO_ANIM_CROUCH_FROM_SLOW_LONGJUMP,
-diff --git a/src/game/mario_actions_submerged.c b/src/game/mario_actions_submerged.c
-index f03e4a93..3626bad0 100644
---- a/src/game/mario_actions_submerged.c
-+++ b/src/game/mario_actions_submerged.c
-@@ -1,5 +1,6 @@
- #include
-
-+#include "mario_cheats.h"
- #include "sm64.h"
- #include "level_update.h"
- #include "memory.h"
-@@ -231,6 +232,8 @@ static void update_swimming_speed(struct MarioState *m, f32 decelThreshold) {
- f32 buoyancy = get_buoyancy(m);
- f32 maxSpeed = 28.0f;
-
-+ cheats_swimming_speed(m);
-+
- if (m->action & ACT_FLAG_STATIONARY) {
- m->forwardVel -= 2.0f;
- }
-diff --git a/src/game/mario_cheats.c b/src/game/mario_cheats.c
-new file mode 100644
-index 00000000..aafa5ce9
---- /dev/null
-+++ b/src/game/mario_cheats.c
-@@ -0,0 +1,884 @@
-+#include
-+#include
-+#include
-+
-+#include "sm64.h"
-+#include "area.h"
-+#include "actors/common0.h"
-+#include "actors/common1.h"
-+#include "actors/group0.h"
-+#include "actors/group1.h"
-+#include "actors/group2.h"
-+#include "actors/group4.h"
-+#include "actors/group5.h"
-+#include "actors/group6.h"
-+#include "actors/group7.h"
-+#include "actors/group9.h"
-+#include "actors/group10.h"
-+#include "actors/group11.h"
-+#include "actors/group12.h"
-+#include "actors/group13.h"
-+#include "actors/group14.h"
-+#include "actors/group15.h"
-+#include "actors/group17.h"
-+#include "audio/data.h"
-+#include "audio/external.h"
-+#include "behavior_actions.h"
-+#include "behavior_data.h"
-+#include "camera.h"
-+#include "engine/behavior_script.h"
-+#include "engine/graph_node.h"
-+#include "engine/level_script.h"
-+#include "engine/math_util.h"
-+#include "engine/surface_collision.h"
-+#include "game_init.h"
-+#include "interaction.h"
-+#include "level_table.h"
-+#include "level_update.h"
-+#include "macros.h"
-+#include "main.h"
-+#include "mario.h"
-+#include "mario_actions_airborne.h"
-+#include "mario_actions_automatic.h"
-+#include "mario_actions_cutscene.h"
-+#include "mario_actions_moving.h"
-+#include "mario_actions_object.h"
-+#include "mario_actions_stationary.h"
-+#include "mario_actions_submerged.h"
-+#include "mario_cheats.h"
-+#include "mario_misc.h"
-+#include "mario_step.h"
-+#include "memory.h"
-+#include "model_ids.h"
-+#include "object_fields.h"
-+#include "object_helpers.h"
-+#include "object_list_processor.h"
-+#include "print.h"
-+#include "rendering_graph_node.h"
-+#include "save_file.h"
-+#include "seq_ids.h"
-+#include "sound_init.h"
-+#include "debug.h"
-+#include "thread6.h"
-+#include "pc/configfile.h"
-+#include "pc/cheats.h"
-+#ifdef R96
-+#include "sgi/utils/characters.h"
-+#endif // R96
-+
-+
-+#define SwiftSwim 42.0f
-+
-+/*SwiftSwim Cheat*/
-+void cheats_swimming_speed(struct MarioState* m) {
-+ while (m->forwardVel < SwiftSwim && Cheats.EnableCheats == true && Cheats.Swim == true) {
-+ while (m->controller->buttonDown & A_BUTTON) {
-+ m->particleFlags |= PARTICLE_BUBBLE;
-+ m->forwardVel += 5.0f;
-+ break;
-+ }
-+ break;
-+ }
-+}
-+
-+/*BLJAnywhere Cheat*/
-+void cheats_air_step(struct MarioState *m) {
-+ if (Cheats.BLJAnywhere > 0 && Cheats.EnableCheats == TRUE && m->action == ACT_LONG_JUMP
-+ && m->forwardVel < 1.0f && m->pos[1] - 50.0f < m->floorHeight) {
-+ if (Cheats.BLJAnywhere < 7) {
-+ if (m->controller->buttonPressed & A_BUTTON) {
-+ m->forwardVel -= (Cheats.BLJAnywhere - 1) * 2.5f;
-+ m->vel[1] = -50.0f;
-+ }
-+ } else if (m->controller->buttonDown & A_BUTTON) {
-+ m->forwardVel -= (Cheats.BLJAnywhere - 7) * 2.5f;
-+ m->vel[1] = -50.0f;
-+ }
-+ }
-+}
-+
-+void cheats_long_jump(struct MarioState *m) {
-+ if (Cheats.BLJAnywhere >= 7 && Cheats.EnableCheats == true && m->forwardVel < 1.0f
-+ && (m->controller->buttonDown & A_BUTTON)) {
-+ set_jumping_action(m, ACT_LONG_JUMP, 0);
-+ }
-+}
-+
-+/*Main cheat function*/
-+void cheats_mario_inputs(struct MarioState *m) {
-+ m->particleFlags = 0;
-+ m->collidedObjInteractTypes = m->marioObj->collidedObjInteractTypes;
-+ m->flags &= 0xFFFFFF;
-+ u32 r;
-+
-+ while (Cheats.EnableCheats == true) {
-+
-+ /*Drain JRB?*/
-+ f32 watLev;
-+ watLev -= 800;
-+ if (WAT_CON == 1) {
-+ watLev += WAT_LEV * 100;
-+ }
-+ if (WAT_CON == 1 && gCurrLevelNum == LEVEL_JRB) {
-+ gEnvironmentRegions[6] = approach_f32_symmetric(gEnvironmentRegions[6], watLev * 10, 10.0f);
-+ gEnvironmentRegions[12] = approach_f32_symmetric(gEnvironmentRegions[12], watLev * 10, 10.0f);
-+ }
-+
-+ /*Coin Magnet*/
-+ struct Object* coinMag = cur_obj_nearest_object_with_behavior(bhvYellowCoin);
-+ struct Object* coinMagMove = cur_obj_nearest_object_with_behavior(bhvMovingYellowCoin);
-+ f32 oDist;
-+ f32 oDistMove;
-+ if (coinMag != NULL && m->marioObj != NULL) {
-+ oDist = dist_between_objects(coinMag, m->marioObj);
-+ }
-+ if (coinMagMove != NULL && m->marioObj != NULL) {
-+ oDistMove = dist_between_objects(coinMagMove, m->marioObj);
-+ }
-+ while (COIN_MAG == 1 && oDist != 0 && oDist >= 100 && oDist < 1000) {
-+ while (oDist >= 10) {
-+ coinMag->oPosX = approach_f32_symmetric(coinMag->oPosX, m->pos[0], 28);
-+ coinMag->oPosY = approach_f32_symmetric(coinMag->oPosY, m->pos[1], 28);
-+ coinMag->oPosZ = approach_f32_symmetric(coinMag->oPosZ, m->pos[2], 28);
-+ break;
-+ }
-+ break;
-+ }
-+ if (oDist == 0 && oDist > 1000) {
-+ obj_mark_for_deletion(coinMag);
-+ break;
-+ }
-+ while (COIN_MAG == 1 && coinMagMove != NULL && oDistMove >= 100 && oDistMove < 1000) {
-+ while (oDistMove >= 10) {
-+ coinMagMove->oPosX = approach_f32_symmetric(coinMagMove->oPosX, m->pos[0], 28);
-+ coinMagMove->oPosY = approach_f32_symmetric(coinMagMove->oPosY, m->pos[1], 28);
-+ coinMagMove->oPosZ = approach_f32_symmetric(coinMagMove->oPosZ, m->pos[2], 28);
-+ break;
-+ }
-+ break;
-+ }
-+ if (oDistMove == 0 && oDistMove > 1000) {
-+ obj_mark_for_deletion(coinMagMove);
-+ break;
-+ }
-+
-+ /*Swim Anywhere*/
-+ if (SWIM_ANY == 1) {
-+ set_submerged_cam_preset_and_spawn_bubbles(m);
-+ m->waterLevel = m->pos[1] + 300;
-+ }
-+
-+ /*No Hold Heavy*/
-+ if (NO_HEAVY == 1) {
-+ while ((m->action & ACT_GROUP_MASK) == ACT_GROUP_MOVING) {
-+ if (m->action == ACT_HOLD_HEAVY_WALKING) {
-+ set_mario_action(m, ACT_HOLD_WALKING, 0);
-+ break;
-+ }
-+ break;
-+ }
-+ }
-+
-+ /*CHAOS Mode*/
-+ if (CHAOS_MODE == 1) {
-+ srand(time(NULL));
-+ r = rand();
-+
-+ switch ((rand() % 30)) {
-+ case 0:
-+ if (Cheats.Run <= 3) {
-+ Cheats.Run += 1;
-+ } else if (Cheats.Run >= 4) {
-+ Cheats.Run = 0;
-+ }
-+ break;
-+ case 1:
-+ if (Cheats.HugeMario == true) {
-+ Cheats.HugeMario = false;
-+ } else {
-+ Cheats.HugeMario = true;
-+ }
-+ break;
-+ case 2:
-+ if (Cheats.TinyMario == true) {
-+ Cheats.TinyMario = false;
-+ } else {
-+ Cheats.TinyMario = true;
-+ }
-+ break;
-+ case 3:
-+ if (Cheats.Moon == true) {
-+ Cheats.Moon = false;
-+ } else {
-+ Cheats.Moon = true;
-+ }
-+ break;
-+ case 4:
-+ if (Cheats.Jump == true) {
-+ Cheats.Jump = false;
-+ } else {
-+ Cheats.Jump = true;
-+ }
-+ break;
-+ case 5:
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_AMP, bhvHomingAmp);
-+ break;
-+ case 6:
-+ if (Cheats.Triple == true) {
-+ Cheats.Triple = false;
-+ } else {
-+ Cheats.Triple = true;
-+ }
-+ break;
-+ case 7:
-+ obj_spawn_yellow_coins(m->marioObj, 1);
-+ break;
-+ case 8:
-+ if (Cheats.PAC <= 6) {
-+ Cheats.PAC += 1;
-+ } else if (Cheats.PAC >= 7) {
-+ Cheats.PAC = 0;
-+ }
-+ break;
-+ case 9:
-+ hurt_and_set_mario_action(m, ACT_FORWARD_AIR_KB, 0, 0);
-+ break;
-+ case 10:
-+ cur_obj_shake_screen(SHAKE_POS_SMALL);
-+ break;
-+ case 11:
-+ m->forwardVel = (m->forwardVel + 5.0f);
-+ break;
-+ case 12:
-+ m->vel[1] -= 5;
-+ break;
-+ case 13:
-+ // Empty Slot
-+ break;
-+ case 14:
-+ // Empty Slot
-+ break;
-+ case 15:
-+ // Empty Slot
-+ break;
-+ case 16:
-+ case 17:
-+ case 18:
-+ case 19:
-+ case 20:
-+ case 21:
-+ case 22:
-+ case 23:
-+ case 24:
-+ case 25:
-+ case 26:
-+ case 27:
-+ case 28:
-+ case 29:
-+ case 30:
-+ print_text(240, 92, " ");
-+ break;
-+ }
-+ }
-+
-+ /*Time Stop Cheat*/
-+ if (Cheats.TimeStop) {
-+ enable_time_stop();
-+ } else if (!Cheats.TimeSlow) {
-+ gTimeStopState &= ~(TIME_STOP_ENABLED);
-+ }
-+ /*Slow Mode*/
-+ while (Cheats.TimeSlow) {
-+ enable_time_stop();
-+ break;
-+ }
-+ if (m->controller->buttonDown & TIME_BUTTON) {
-+ Cheats.TimeSlow = true;
-+ } else {
-+ if (Cheats.TimeStop) {
-+ enable_time_stop();
-+ } else
-+ if (gTimeStopState &= ~(TIME_STOP_MARIO_AND_DOORS)) {
-+ gTimeStopState &= ~(TIME_STOP_ENABLED);
-+ }
-+ }
-+
-+
-+ /*FLYER Cheat*/
-+ if (Cheats.Fly) {
-+ if (m->action == ACT_FLYING) {
-+ if (m->controller->buttonDown & A_BUTTON) {
-+ m->particleFlags |= PARTICLE_SPARKLES;
-+ }
-+ }
-+ }
-+
-+ /*Coin Spawner*/
-+ switch (Cheats.Coin) {
-+ case 0:
-+ break;
-+ case 1:
-+ if (m->controller->buttonDown & B_BUTTON) {
-+ obj_spawn_yellow_coins(m->marioObj, 1);
-+ break;
-+ }
-+ break;
-+ case 2:
-+ if (m->controller->buttonDown & B_BUTTON) {
-+ spawn_object(m->marioObj, MODEL_BLUE_COIN, bhvBlueCoinJumping);
-+ break;
-+ }
-+ break;
-+ case 3:
-+ if (m->controller->buttonDown & B_BUTTON) {
-+ spawn_object_relative(0, 0, 70, 150, m->marioObj, MODEL_RED_COIN, bhvRedCoin);
-+ break;
-+ }
-+ break;
-+ }
-+
-+ /*All Jumps Triple Cheat*/
-+ while (Cheats.Triple && (m->action & ACT_GROUP_MASK) != ACT_GROUP_SUBMERGED) {
-+ // While Triple Jump Cheat is true and Mario's is not underwater
-+ if (m->controller->buttonPressed & A_BUTTON && m->action != ACT_TRIPLE_JUMP) {
-+ // If A is pressed and not already triple jumping
-+ set_mario_action(m, ACT_TRIPLE_JUMP, 0);
-+ // Break out of the while
-+ break;
-+ }
-+ // Break out of the while
-+ break;
-+ }
-+
-+ /*Hover Cheat*/
-+ if (Cheats.Hover) {
-+ set_mario_action(m, ACT_DEBUG_FREE_MOVE, 0);
-+ Cheats.Hover = false;
-+ }
-+
-+ /*Moon Gravity*/
-+ while (Cheats.Moon) {
-+ while ((m->action & ACT_GROUP_MASK) == ACT_GROUP_AIRBORNE) {
-+ if (m->action != ACT_FREEFALL && m->action != ACT_LONG_JUMP) {
-+ m->vel[1] += 2;
-+ break;
-+ } else {
-+ m->vel[1] += 1;
-+ break;
-+ }
-+ break;
-+ }
-+ break;
-+ }
-+ /*Jump Modifier*/
-+ while (Cheats.Jump) {
-+ while ((m->action & ACT_GROUP_MASK) == ACT_GROUP_AIRBORNE) {
-+ if (m->action != ACT_FREEFALL) {
-+ m->vel[1] += 1;
-+ break;
-+ }
-+ if (m->action &= ACT_FREEFALL) {
-+ m->vel[1] -= 5;
-+ break;
-+ }
-+ break;
-+ }
-+ break;
-+ }
-+
-+ /*Run Modifier Cheat*/
-+ switch (Cheats.Run) {
-+ case 0:
-+ break;
-+ case 1:
-+ if (m->action == ACT_WALKING && m->forwardVel >= 0) {
-+ m->forwardVel = (m->forwardVel - 0.5f);
-+ }
-+ break;
-+ case 2:
-+ if (m->action == ACT_WALKING && m->forwardVel >= 0) {
-+ m->forwardVel = (m->forwardVel - 0.7f);
-+ }
-+ break;
-+ case 3:
-+ if (m->action == ACT_WALKING && m->forwardVel >= 0) {
-+ m->forwardVel = (m->forwardVel * 1.2f);
-+ }
-+ break;
-+ case 4:
-+ if (m->action == ACT_WALKING && m->forwardVel >= 0) {
-+ m->forwardVel = (m->forwardVel * 1.8f);
-+ }
-+ break;
-+ }
-+ /*Play As Cheat*/
-+ switch(Cheats.PAC) {
-+ /*Model Choices*/
-+ case 0:
-+#ifdef R96
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_PLAYER];
-+ if (isLuigi() == 1) {
-+ gMarioState->animation = &Data_LuigiAnims;
-+ } else {
-+ gMarioState->animation = &D_80339D10;
-+ }
-+ break;
-+#else
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_MARIO]; //Use MODEL_PLAYER
-+ m->animation = &D_80339D10; //Only Mario's animations
-+ break;
-+#endif // R96
-+ case 1:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_BLACK_BOBOMB];
-+ m->marioObj->header.gfx.unk38.curAnim = bobomb_seg8_anims_0802396C[0];
-+ break;
-+ case 2:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_BOBOMB_BUDDY];
-+ m->marioObj->header.gfx.unk38.curAnim = bobomb_seg8_anims_0802396C[0];
-+ break;
-+ case 3:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_GOOMBA];
-+ m->marioObj->header.gfx.unk38.curAnim = goomba_seg8_anims_0801DA4C[0];
-+ break;
-+ case 4:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_KOOPA_SHELL];
-+ m->marioObj->header.gfx.unk38.curAnim = amp_seg8_anims_08004034[0];
-+ break;
-+ case 5:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_CHUCKYA];
-+ m->marioObj->header.gfx.unk38.curAnim = chuckya_seg8_anims_0800C070[0];
-+ break; //Forgot this in v7
-+ case 6:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_FLYGUY];
-+ m->marioObj->header.gfx.unk38.curAnim = flyguy_seg8_anims_08011A64[0];
-+ break;
-+ case 7:
-+ switch (gCurrLevelNum) {
-+ case LEVEL_CASTLE_GROUNDS:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_YOSHI];
-+ m->marioObj->header.gfx.unk38.curAnim = yoshi_seg5_anims_05024100[0];
-+ break;
-+ case LEVEL_BOB:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_KOOPA_WITH_SHELL];
-+ m->marioObj->header.gfx.unk38.curAnim = koopa_seg6_anims_06011364[0];
-+ m->marioObj->header.gfx.unk38.animFrame += 1;
-+ break;
-+ case LEVEL_BBH:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_MAD_PIANO];
-+ m->marioObj->header.gfx.unk38.curAnim = mad_piano_seg5_anims_05009B14[0];
-+ break;
-+ case LEVEL_WF:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_WHOMP];
-+ m->marioObj->header.gfx.unk38.curAnim = whomp_seg6_anims_06020A04[0];
-+ break;
-+ case LEVEL_JRB:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_CLAM_SHELL];
-+ m->marioObj->header.gfx.unk38.curAnim = clam_shell_seg5_anims_05001744[0];
-+ break;
-+ case LEVEL_CCM:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_PENGUIN];
-+ m->marioObj->header.gfx.unk38.curAnim = penguin_seg5_anims_05008B74[0];
-+ break;
-+ case LEVEL_PSS:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_WOODEN_SIGNPOST];
-+ break;
-+ case LEVEL_HMC:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_HMC_ROLLING_ROCK];
-+ break;
-+ case LEVEL_LLL:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_BULLY];
-+ m->marioObj->header.gfx.unk38.curAnim = bully_seg5_anims_0500470C[0];
-+ break;
-+ case LEVEL_SSL:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_KLEPTO];
-+ m->marioObj->header.gfx.unk38.curAnim = klepto_seg5_anims_05008CFC[0];
-+ break;
-+ case LEVEL_DDD:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_SUSHI];
-+ m->marioObj->header.gfx.unk38.curAnim = sushi_seg5_anims_0500AE54[0];
-+ break;
-+ case LEVEL_SL:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_SPINDRIFT];
-+ m->marioObj->header.gfx.unk38.curAnim = spindrift_seg5_anims_05002D68[0];
-+ break;
-+ case LEVEL_WDW:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_SKEETER];
-+ m->marioObj->header.gfx.unk38.curAnim = skeeter_seg6_anims_06007DE0[0];
-+ break;
-+ case LEVEL_TTM:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_UKIKI];
-+ m->marioObj->header.gfx.unk38.curAnim = ukiki_seg5_anims_05015784[0];
-+ break;
-+ case LEVEL_THI:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_SPINY];
-+ m->marioObj->header.gfx.unk38.curAnim = spiny_seg5_anims_05016EAC[0];
-+ break;
-+ case LEVEL_TTC:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_THWOMP];
-+ break;
-+ case LEVEL_RR:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_ENEMY_LAKITU];
-+ m->marioObj->header.gfx.unk38.curAnim = lakitu_enemy_seg5_anims_050144D4[0];
-+ break;
-+ case LEVEL_SA:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_MANTA_RAY];
-+ m->marioObj->header.gfx.unk38.curAnim = manta_seg5_anims_05008EB4[0];
-+ break;
-+ case LEVEL_COTMC:
-+ m->marioObj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_SNUFIT];
-+ break;
-+ }
-+ }
-+ while (Cheats.PAC > 0) {
-+ /*stops softlock when dying as a Play As Character*/
-+ if (m->action == ACT_STANDING_DEATH) {
-+ level_trigger_warp(m, WARP_OP_DEATH);
-+ m->numLives += 1;
-+ update_mario_health(m);
-+ break;
-+ }
-+ /*Instead of making a custom hitbox for each character,
-+ I neutralized the only consistent problem, doors*/
-+ while (m->collidedObjInteractTypes & INTERACT_DOOR) {
-+ obj_mark_for_deletion(m->usedObj);
-+ spawn_object(gCurrentObject, MODEL_SMOKE, bhvBobombBullyDeathSmoke);
-+ obj_scale(gCurrentObject, gCurrentObject->oTimer / 4.f + 1.0f);
-+ gCurrentObject->oOpacity -= 14;
-+ gCurrentObject->oAnimState++;
-+ play_sound(SOUND_GENERAL2_BOBOMB_EXPLOSION, m->marioObj->header.gfx.cameraToObject);
-+ m->particleFlags |= PARTICLE_TRIANGLE;
-+ obj_set_pos(m->marioObj, 0, 0, 100);
-+ break;
-+ }
-+ break;
-+ }
-+
-+
-+ /*Speed Display*/
-+ if (Cheats.SPD == true) {
-+ print_text_fmt_int(210, 72, "SPD %d", m->forwardVel);
-+ }
-+
-+ /*T Pose Float? Actually it's just twirling + MoonJump*/
-+ while (Cheats.TPF == true) {
-+ if (m->controller->buttonDown & A_BUTTON) {
-+ m->vel[1] = 25;
-+ set_mario_action(m, ACT_TWIRLING, 0);
-+ }
-+ break;
-+ }
-+
-+ /*QuickEnding cheat*/
-+ while (Cheats.QuikEnd == true) {
-+ if (m->numStars == 120) {
-+ level_trigger_warp(m, WARP_OP_CREDITS_START);
-+ Cheats.QuikEnd = false;
-+ save_file_do_save(gCurrSaveFileNum - 1);
-+ }
-+ break;
-+ }
-+
-+ /*AutoWallKick cheat*/
-+ if (Cheats.AutoWK == true && m->action == ACT_AIR_HIT_WALL) {
-+ m->vel[1] = 52.0f;
-+ m->faceAngle[1] += 0x8000;
-+ set_mario_action(m, ACT_WALL_KICK_AIR, 0);
-+ m->wallKickTimer = 0;
-+ set_mario_animation(m, MARIO_ANIM_START_WALLKICK);
-+ }
-+
-+ /*HurtMario cheat*/
-+ while (Cheats.Hurt > 0 && m->controller->buttonDown & L_TRIG
-+ && m->controller->buttonPressed & A_BUTTON) {
-+ if (Cheats.Hurt == 1) {
-+ act_burning_ground(m);
-+ }
-+ if (Cheats.Hurt == 2) {
-+ m->flags |= MARIO_METAL_SHOCK;
-+ drop_and_set_mario_action(m, ACT_SHOCKED, 0);
-+ }
-+ if (Cheats.Hurt == 3) {
-+ hurt_and_set_mario_action(m, ACT_GROUND_BONK, 0, 1);
-+ play_sound(SOUND_MARIO_OOOF, m->marioObj->header.gfx.cameraToObject);
-+ queue_rumble_data(5, 80);
-+ }
-+ break;
-+ }
-+
-+ /*CannonAnywhere cheat*/
-+ if (Cheats.Cann == true && m->controller->buttonDown & L_TRIG
-+ && m->controller->buttonPressed & U_CBUTTONS) {
-+ spawn_object_relative(1, 0, 200, 0, gCurrentObject, MODEL_NONE, bhvCannon);
-+ }
-+
-+ /*InstantDeath cheat*/
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonDown & A_BUTTON
-+ && m->controller->buttonPressed & B_BUTTON && m->controller->buttonDown & Z_TRIG) {
-+ level_trigger_warp(m, WARP_OP_DEATH);
-+ break;
-+ }
-+
-+ /*CAP Cheats, this whole thing needs to be refactored, but
-+ I've only been adding to JAGSTAX's original patch*/
-+ if (Cheats.EnableCheats) {
-+ if (Cheats.WingCap) {
-+ m->flags |= MARIO_WING_CAP;
-+ if ((m->action & ACT_GROUP_MASK) == (!(ACT_GROUP_AIRBORNE) && !(ACT_GROUP_SUBMERGED))) {
-+ set_mario_action(m, ACT_PUTTING_ON_CAP, 0);
-+ }
-+ Cheats.WingCap = false;
-+ }
-+
-+ if (Cheats.MetalCap) {
-+ m->flags |= MARIO_METAL_CAP;
-+ if ((m->action & ACT_GROUP_MASK) == (!(ACT_GROUP_AIRBORNE) && !(ACT_GROUP_SUBMERGED))) {
-+ set_mario_action(m, ACT_PUTTING_ON_CAP, 0);
-+ }
-+ Cheats.MetalCap = false;
-+ }
-+
-+ if (Cheats.VanishCap) {
-+ m->flags |= MARIO_VANISH_CAP;
-+ if ((m->action & ACT_GROUP_MASK) == (!(ACT_GROUP_AIRBORNE) && !(ACT_GROUP_SUBMERGED))) {
-+ set_mario_action(m, ACT_PUTTING_ON_CAP, 0);
-+ }
-+ Cheats.VanishCap = false;
-+ }
-+
-+ if (Cheats.RemoveCap) {
-+ m->flags &= ~MARIO_CAP_ON_HEAD;
-+ m->flags |= MARIO_CAP_IN_HAND;
-+ if ((m->action & ACT_GROUP_MASK) == (!(ACT_GROUP_AIRBORNE) && !(ACT_GROUP_SUBMERGED))) {
-+ set_mario_action(m, ACT_SHIVERING, 0);
-+ }
-+ Cheats.RemoveCap = false;
-+ }
-+
-+ if (Cheats.NormalCap) {
-+ m->flags &= ~MARIO_CAP_ON_HEAD;
-+ m->flags &= ~(MARIO_WING_CAP | MARIO_METAL_CAP | MARIO_VANISH_CAP);
-+ if ((m->action & ACT_GROUP_MASK) == (!(ACT_GROUP_AIRBORNE) && !(ACT_GROUP_SUBMERGED))) {
-+ m->flags |= MARIO_CAP_IN_HAND;
-+ set_mario_action(m, ACT_PUTTING_ON_CAP, 0);
-+ } else {
-+ m->flags &= ~MARIO_CAP_IN_HAND;
-+ m->flags |= MARIO_CAP_ON_HEAD;
-+ }
-+ Cheats.NormalCap = false;
-+ }
-+ }
-+
-+ /* GetShell cheat */
-+ while (Cheats.GetShell == true && m->controller->buttonDown & L_TRIG
-+ && m->controller->buttonPressed & R_TRIG) {
-+ if (m->action & ACT_FLAG_RIDING_SHELL) {
-+ break;
-+ }
-+
-+ /*This check should be added when creating a spawn cheat to prevent spamming*/
-+ struct Object *obj = (struct Object *) gObjectLists[OBJ_LIST_LEVEL].next;
-+ struct Object *first = (struct Object *) &gObjectLists[OBJ_LIST_LEVEL];
-+ while (obj != NULL && obj != first) {
-+ if (obj->header.gfx.sharedChild = gLoadedGraphNodes[MODEL_KOOPA_SHELL]) {
-+ obj_mark_for_deletion(obj);
-+ break;
-+ }
-+ obj = (struct Object *) obj->header.next;
-+ }
-+
-+ if ((m->action & ACT_GROUP_MASK) == ACT_GROUP_SUBMERGED) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_KOOPA_SHELL,
-+ bhvKoopaShellUnderwater);
-+ break;
-+ } else {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_KOOPA_SHELL, bhvKoopaShell);
-+ break;
-+ }
-+ }
-+
-+ /* GetBobomb cheat */
-+ while (Cheats.GetBob == true && m->controller->buttonDown & L_TRIG
-+ && m->controller->buttonPressed & B_BUTTON) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_BLACK_BOBOMB, bhvBobomb);
-+ break;
-+ }
-+
-+ /* SpawnCommon0 aka Spamba cheat*/
-+ switch (Cheats.Spamba) {
-+ case 0:
-+ break;
-+ case 1:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_AMP, bhvHomingAmp);
-+ break;
-+ }
-+ break;
-+ case 2:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 0, 150, gCurrentObject, MODEL_BLUE_COIN_SWITCH,
-+ bhvBlueCoinSwitch);
-+ break;
-+ }
-+ break;
-+ case 3:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 300, 300, gCurrentObject, MODEL_BOWLING_BALL,
-+ bhvPitBowlingBall);
-+ break;
-+ }
-+ break;
-+ case 4:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 0, 200, gCurrentObject, MODEL_BREAKABLE_BOX,
-+ bhvBreakableBox);
-+ break;
-+ }
-+ break;
-+ case 5:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 50, 100, gCurrentObject, MODEL_BREAKABLE_BOX_SMALL,
-+ bhvBreakableBoxSmall);
-+ break;
-+ }
-+ break;
-+ case 6:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 10, 300, gCurrentObject, MODEL_BREAKABLE_BOX_SMALL,
-+ bhvJumpingBox);
-+ break;
-+ }
-+ break;
-+ case 7:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, -10, 100, gCurrentObject, MODEL_CHECKERBOARD_PLATFORM,
-+ bhvCheckerboardPlatformSub);
-+ break;
-+ }
-+ break;
-+ case 8:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_CHUCKYA, bhvChuckya);
-+ break;
-+ }
-+ break;
-+ case 9:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_FLYGUY, bhvFlyGuy);
-+ break;
-+ }
-+ break;
-+ case 10:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_NONE,
-+ bhvGoombaTripletSpawner);
-+ break;
-+ }
-+ break;
-+ case 11:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 100, 100, gCurrentObject, MODEL_HEART,
-+ bhvRecoveryHeart);
-+ break;
-+ }
-+ break;
-+ case 12:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 0, 200, gCurrentObject, MODEL_METAL_BOX,
-+ bhvPushableMetalBox);
-+ break;
-+ }
-+ break;
-+ case 13:
-+ if (m->controller->buttonDown & L_TRIG && m->controller->buttonPressed & Z_TRIG) {
-+ spawn_object_relative(0, 0, 50, 50, gCurrentObject, MODEL_PURPLE_SWITCH,
-+ bhvPurpleSwitchHiddenBoxes);
-+ break;
-+ }
-+ break;
-+ }
-+
-+ /*Jukebox*/
-+ if (Cheats.JBC) {
-+ /*JBC is the bool, acting like the on/off*/
-+ switch(Cheats.JB) {
-+ case 0:
-+ play_cap_music(SEQ_EVENT_CUTSCENE_INTRO);
-+ Cheats.JBC = false;
-+ break;
-+ case 1:
-+ play_cap_music(SEQ_LEVEL_GRASS);
-+ Cheats.JBC = false;
-+ break;
-+ case 2:
-+ play_cap_music(SEQ_LEVEL_INSIDE_CASTLE);
-+ Cheats.JBC = false;
-+ break;
-+ case 3:
-+ play_cap_music(SEQ_LEVEL_WATER);
-+ Cheats.JBC = false;
-+ break;
-+ case 4:
-+ play_cap_music(SEQ_LEVEL_HOT);
-+ Cheats.JBC = false;
-+ break;
-+ case 5:
-+ play_cap_music(SEQ_LEVEL_BOSS_KOOPA);
-+ Cheats.JBC = false;
-+ break;
-+ case 6:
-+ play_cap_music(SEQ_LEVEL_SNOW);
-+ Cheats.JBC = false;
-+ break;
-+ case 7:
-+ play_cap_music(SEQ_LEVEL_SLIDE);
-+ Cheats.JBC = false;
-+ break;
-+ case 8:
-+ play_cap_music(SEQ_LEVEL_SPOOKY);
-+ Cheats.JBC = false;
-+ break;
-+ case 9:
-+ play_cap_music(SEQ_LEVEL_UNDERGROUND);
-+ Cheats.JBC = false;
-+ break;
-+ case 10:
-+ play_cap_music(SEQ_LEVEL_KOOPA_ROAD);
-+ Cheats.JBC = false;
-+ break;
-+ case 11:
-+ play_cap_music(SEQ_LEVEL_BOSS_KOOPA_FINAL);
-+ Cheats.JBC = false;
-+ break;
-+ case 12:
-+ play_cap_music(SEQ_MENU_TITLE_SCREEN);
-+ Cheats.JBC = false;
-+ break;
-+ case 13:
-+ play_cap_music(SEQ_MENU_FILE_SELECT);
-+ Cheats.JBC = false;
-+ break;
-+ case 14:
-+ play_cap_music(SEQ_EVENT_POWERUP);
-+ Cheats.JBC = false;
-+ break;
-+ case 15:
-+ play_cap_music(SEQ_EVENT_METAL_CAP);
-+ Cheats.JBC = false;
-+ break;
-+ case 16:
-+ play_cap_music(SEQ_EVENT_BOSS);
-+ Cheats.JBC = false;
-+ break;
-+ case 17:
-+ play_cap_music(SEQ_EVENT_MERRY_GO_ROUND);
-+ Cheats.JBC = false;
-+ break;
-+ case 18:
-+ play_cap_music(SEQ_EVENT_CUTSCENE_CREDITS);
-+ Cheats.JBC = false;
-+ break;
-+ }
-+ }
-+ break;
-+ }
-+}
-\ No newline at end of file
-diff --git a/src/game/mario_cheats.h b/src/game/mario_cheats.h
-new file mode 100644
-index 00000000..a091d3de
---- /dev/null
-+++ b/src/game/mario_cheats.h
-@@ -0,0 +1,40 @@
-+#ifndef MARIO_CHEATS_H
-+#define MARIO_CHEATS_H
-+
-+#include
-+
-+#include "macros.h"
-+#include "model_ids.h"
-+#include "types.h"
-+
-+#if defined(MODEL_PLAYER) && defined(MODEL_LUIGIS_CAP)
-+#define R96
-+#endif
-+
-+void cheats_set_model(struct MarioState *m);
-+void cheats_swimming_speed(struct MarioState *m);
-+void cheats_air_step(struct MarioState *m);
-+void cheats_long_jump(struct MarioState *m);
-+void cheats_mario_inputs(struct MarioState *m);
-+
-+/* Options */
-+#define TIME_BUTTON 0x0080
-+
-+#include "pc/dynamic_options.h"
-+#define __chaos_mode__ dynos_get_value("chaos_mode")
-+#define __time_button__ dynos_get_value("time_button")
-+#define __no_heavy__ dynos_get_value("no_heavy")
-+#define __haz_walk__ dynos_get_value("haz_walk")
-+#define __swim_any__ dynos_get_value("swim_any")
-+#define __coin_mag__ dynos_get_value("coin_mag")
-+#define __wat_con__ dynos_get_value("wat_con")
-+#define __wat_lev__ dynos_get_value("wat_lev")
-+#define CHAOS_MODE (__chaos_mode__ == 1)
-+#define NO_HEAVY (__no_heavy__ == 1)
-+#define HAZ_WALK (__haz_walk__ == 1)
-+#define SWIM_ANY (__swim_any__ == 1)
-+#define COIN_MAG (__coin_mag__ == 1)
-+#define WAT_CON (__wat_con__ == 1)
-+#define WAT_LEV (__wat_lev__)
-+
-+#endif // MARIO_CHEATS_H
-diff --git a/src/game/mario_misc.c b/src/game/mario_misc.c
-index e6354e89..bc5a9820 100644
---- a/src/game/mario_misc.c
-+++ b/src/game/mario_misc.c
-@@ -1,6 +1,7 @@
- #include
-
- #include "sm64.h"
-+#include "pc/cheats.h"
- #include "area.h"
- #include "audio/external.h"
- #include "behavior_actions.h"
-@@ -232,7 +233,11 @@ void bhv_unlock_door_star_init(void) {
- gCurrentObject->oUnlockDoorStarTimer = 0;
- gCurrentObject->oUnlockDoorStarYawVel = 0x1000;
- gCurrentObject->oPosX += 30.0f * sins(gMarioState->faceAngle[1] - 0x4000);
-- gCurrentObject->oPosY += 160.0f;
-+ if (Cheats.EnableCheats && Cheats.PAC > 0) {
-+ gCurrentObject->oPosY += 120.0f;
-+ } else {
-+ gCurrentObject->oPosY += 160.0f;
-+ }
- gCurrentObject->oPosZ += 30.0f * coss(gMarioState->faceAngle[1] - 0x4000);
- gCurrentObject->oMoveAngleYaw = 0x7800;
- obj_scale(gCurrentObject, 0.5f);
-diff --git a/src/game/mario_step.c b/src/game/mario_step.c
-index ba8315ca..85c06be2 100644
---- a/src/game/mario_step.c
-+++ b/src/game/mario_step.c
-@@ -1,5 +1,7 @@
- #include
-
-+#include "pc/cheats.h"
-+#include "mario_cheats.h"
- #include "sm64.h"
- #include "engine/math_util.h"
- #include "engine/surface_collision.h"
-@@ -105,6 +107,9 @@ void mario_bonk_reflection(struct MarioState *m, u32 negateSpeed) {
- }
-
- u32 mario_update_quicksand(struct MarioState *m, f32 sinkingSpeed) {
-+ if (Cheats.EnableCheats && HAZ_WALK == 1) {
-+ m->quicksandDepth = 0.0f;
-+ } else
- if (m->action & ACT_FLAG_RIDING_SHELL) {
- m->quicksandDepth = 0.0f;
- } else {
-diff --git a/src/game/options_menu.c b/src/game/options_menu.c
-index e2ce325d..7b0b968d 100644
---- a/src/game/options_menu.c
-+++ b/src/game/options_menu.c
-@@ -1,5 +1,5 @@
- #ifdef EXT_OPTIONS_MENU
--
-+#ifdef DYNOS_INL
- #include "sm64.h"
- #include "engine/math_util.h"
- #include "audio/external.h"
-@@ -13,6 +13,7 @@
- #endif
- #include "game/mario_misc.h"
- #include "game/game_init.h"
-+#include "game/cheats_menu.h"
- #include "game/ingame_menu.h"
- #include "game/options_menu.h"
- #include "pc/pc_main.h"
-@@ -223,6 +224,33 @@ static void optvideo_apply(UNUSED struct Option *self, s32 arg) {
- if (!arg) configWindow.settings_changed = true;
- }
-
-+
-+static void setCap_Wing(UNUSED struct Option *self, s32 arg) {
-+ if (!arg) Cheats.WingCap = true;
-+}
-+static void setCap_Metal(UNUSED struct Option *self, s32 arg) {
-+ if (!arg) Cheats.MetalCap = true;
-+}
-+static void setCap_Vanish(UNUSED struct Option *self, s32 arg) {
-+ if (!arg) Cheats.VanishCap = true;
-+}
-+static void setCap_Remove(UNUSED struct Option *self, s32 arg) {
-+ if (!arg) Cheats.RemoveCap = true;
-+}
-+static void setCap_Normal(UNUSED struct Option *self, s32 arg) {
-+ Cheats.WingCap = false;
-+ Cheats.MetalCap = false;
-+ Cheats.VanishCap = false;
-+ Cheats.RemoveCap = false;
-+ if (!arg) Cheats.NormalCap = true;
-+}
-+static void setJBC(UNUSED struct Option *self, s32 arg) {
-+ if (!arg)
-+ Cheats.JBC = true;
-+}
-+
-+
-+
- /* submenu option lists */
-
- #ifdef BETTERCAMERA
-@@ -282,15 +310,45 @@ static struct Option optsAudio[] = {
- };
-
- static struct Option optsCheats[] = {
-- DEF_OPT_TOGGLE( optsCheatsStr[0], &Cheats.EnableCheats ),
-- DEF_OPT_TOGGLE( optsCheatsStr[1], &Cheats.MoonJump ),
-- DEF_OPT_TOGGLE( optsCheatsStr[2], &Cheats.GodMode ),
-- DEF_OPT_TOGGLE( optsCheatsStr[3], &Cheats.InfiniteLives ),
-- DEF_OPT_TOGGLE( optsCheatsStr[4], &Cheats.SuperSpeed ),
-- DEF_OPT_TOGGLE( optsCheatsStr[5], &Cheats.Responsive ),
-- DEF_OPT_TOGGLE( optsCheatsStr[6], &Cheats.ExitAnywhere ),
-- DEF_OPT_TOGGLE( optsCheatsStr[7], &Cheats.HugeMario ),
-- DEF_OPT_TOGGLE( optsCheatsStr[8], &Cheats.TinyMario ),
-+ DEF_OPT_TOGGLE(optsCheatsStr[0], &Cheats.EnableCheats),
-+ DEF_OPT_TOGGLE(optsCheatsStr[1], &Cheats.MoonJump),
-+ DEF_OPT_TOGGLE(optsCheatsStr[2], &Cheats.GodMode),
-+ DEF_OPT_TOGGLE(optsCheatsStr[3], &Cheats.InfiniteLives),
-+ DEF_OPT_TOGGLE(optsCheatsStr[4], &Cheats.SuperSpeed),
-+ DEF_OPT_TOGGLE(optsCheatsStr[5], &Cheats.Responsive),
-+ DEF_OPT_TOGGLE(optsCheatsStr[6], &Cheats.ExitAnywhere),
-+ DEF_OPT_TOGGLE(optsCheatsStr[7], &Cheats.HugeMario),
-+ DEF_OPT_TOGGLE(optsCheatsStr[8], &Cheats.TinyMario),
-+ DEF_OPT_CHOICE(optsCheatsStr2[0], &Cheats.Coin, CoinChoices),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[1], &Cheats.Hover),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[2], &Cheats.Moon),
-+ DEF_OPT_CHOICE(optsCheatsStr2[3], &Cheats.Run, SpeedChoices),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[4], &Cheats.NDB),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[5], &Cheats.Jump),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[6], &Cheats.SPD),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[7], &Cheats.TPF),
-+ DEF_OPT_CHOICE(optsCheatsStr2[8], &Cheats.JB, SeqChoices),
-+ DEF_OPT_BUTTON(optsCheatsStr2[9], setJBC),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[10], &Cheats.QuikEnd),
-+ DEF_OPT_CHOICE(optsCheatsStr2[11], &Cheats.Hurt, HurtCheatChoices),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[12], &Cheats.Cann),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[13], &Cheats.AutoWK),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[14], &Cheats.GetShell),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[15], &Cheats.GetBob),
-+ DEF_OPT_CHOICE(optsCheatsStr2[16], &Cheats.Spamba, SpamCheatChoices),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[17], &Cheats.Swim),
-+ DEF_OPT_BUTTON(optsCheatsStr2[18], setCap_Wing),
-+ DEF_OPT_BUTTON(optsCheatsStr2[19], setCap_Metal),
-+ DEF_OPT_BUTTON(optsCheatsStr2[20], setCap_Vanish),
-+ DEF_OPT_BUTTON(optsCheatsStr2[21], setCap_Remove),
-+ DEF_OPT_BUTTON(optsCheatsStr2[22], setCap_Normal),
-+ DEF_OPT_CHOICE(optsCheatsStr2[23], &Cheats.BLJAnywhere, bljCheatChoices),
-+ DEF_OPT_CHOICE(optsCheatsStr2[24], &Cheats.PAC, PlayAsCheatChoices),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[25], &Cheats.Triple),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[26], &Cheats.Fly),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[27], &Cheats.NoBounds),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[28], &Cheats.FLJ),
-+ DEF_OPT_TOGGLE(optsCheatsStr2[29], &Cheats.TimeStop),
-
- };
-
-@@ -652,5 +710,5 @@ void optmenu_check_buttons(void) {
- optmenu_option_timer = 0;
- }
- }
--
-+#endif
- #endif // EXT_OPTIONS_MENU
-diff --git a/src/pc/cheats.h b/src/pc/cheats.h
-index eaf71ab4..42c13432 100644
---- a/src/pc/cheats.h
-+++ b/src/pc/cheats.h
-@@ -13,6 +13,37 @@ struct CheatList {
- bool ExitAnywhere;
- bool HugeMario;
- bool TinyMario;
-+ unsigned int Coin;
-+ bool Hover;
-+ bool Moon;
-+ unsigned int Run;
-+ bool NDB;
-+ bool Jump;
-+ bool SPD;
-+ bool TPF;
-+ unsigned int JB;
-+ bool JBC;
-+ bool QuikEnd;
-+ unsigned int Hurt;
-+ bool Cann;
-+ bool AutoWK;
-+ bool GetShell;
-+ bool GetBob;
-+ unsigned int Spamba;
-+ bool Swim;
-+ bool WingCap;
-+ bool MetalCap;
-+ bool VanishCap;
-+ bool RemoveCap;
-+ bool NormalCap;
-+ unsigned int BLJAnywhere;
-+ unsigned int PAC;
-+ bool Triple;
-+ bool Fly;
-+ bool NoBounds;
-+ bool FLJ;
-+ bool TimeStop;
-+ bool TimeSlow;
- };
-
- extern struct CheatList Cheats;
-diff --git a/src/pc/configfile.c b/src/pc/configfile.c
-index 47111600..461d46e7 100644
---- a/src/pc/configfile.c
-+++ b/src/pc/configfile.c
-@@ -7,6 +7,7 @@
- #include
-
- #include "platform.h"
-+#include "cheats.h"
- #include "configfile.h"
- #include "cliopts.h"
- #include "gfx/gfx_screen_config.h"
-@@ -138,6 +139,31 @@ static const struct ConfigOption options[] = {
- {.name = "bettercam_degrade", .type = CONFIG_TYPE_UINT, .uintValue = &configCameraDegrade},
- #endif
- {.name = "skip_intro", .type = CONFIG_TYPE_BOOL, .boolValue = &configSkipIntro},
-+ {.name = "enable_cheats", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.EnableCheats },
-+ {.name = "moonjump", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.MoonJump },
-+ {.name = "invincible", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.GodMode },
-+ {.name = "infintie_lives", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.InfiniteLives },
-+ {.name = "super_speed", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.SuperSpeed },
-+ {.name = "controls", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Responsive },
-+ {.name = "exit_anywhere", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.ExitAnywhere },
-+ {.name = "huge_mario", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.HugeMario },
-+ {.name = "tiny_mario", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.TinyMario },
-+ {.name = "coin", .type = CONFIG_TYPE_UINT, .uintValue = &Cheats.Coin },
-+ {.name = "hover_mode", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Hover },
-+ {.name = "moon_gravity", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Moon },
-+ {.name = "run_speed", .type = CONFIG_TYPE_UINT, .uintValue = &Cheats.Run },
-+ {.name = "no_death_barrier", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.NDB },
-+ {.name = "jumps_higher", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Jump },
-+ {.name = "speed_display", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.SPD },
-+ {.name = "t_pose_float", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.TPF },
-+ {.name = "cannon_anywhere", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Cann },
-+ {.name = "auto_wk", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.AutoWK },
-+ {.name = "get_shell", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.GetShell },
-+ {.name = "get_bobomb", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.GetBob },
-+ {.name = "swift_swim", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Swim },
-+ {.name = "blj_anywhere", .type = CONFIG_TYPE_UINT, .uintValue = &Cheats.BLJAnywhere },
-+ {.name = "play_as", .type = CONFIG_TYPE_UINT, .uintValue = &Cheats.PAC },
-+ {.name = "flyer", .type = CONFIG_TYPE_BOOL, .boolValue = &Cheats.Fly },
- #ifdef DISCORDRPC
- {.name = "discordrpc_enable", .type = CONFIG_TYPE_BOOL, .boolValue = &configDiscordRPC},
- #endif
-diff --git a/src/pc/dynamic_options.c b/src/pc/dynamic_options.c
-new file mode 100644
-index 00000000..a47514bd
---- /dev/null
-+++ b/src/pc/dynamic_options.c
-@@ -0,0 +1,1637 @@
-+#include
-+#include
-+#include
-+#include "controller/controller_keyboard.h"
-+#include "controller/controller_sdl.h"
-+#include "game/object_list_processor.h"
-+#include "game/spawn_object.h"
-+#include "game/sound_init.h"
-+#include "engine/level_script.h"
-+#include "fs/fs.h"
-+#include "levels.h"
-+#include "utils.h"
-+#include "configfile.h"
-+#include "gfx_dimensions.h"
-+#ifdef RENDER96_2_0
-+#include "text/text-loader.h"
-+#include "text/txtconv.h"
-+#endif
-+
-+static void *make_copy(const void *p, int s) {
-+ void *q = calloc(s, 1);
-+ memcpy(q, p, s);
-+ return q;
-+}
-+
-+//
-+// String utilities
-+//
-+
-+static const char *string_to_configname(const char *str) {
-+ if (str == NULL || strcmp(str, "NULL") == 0 || strcmp(str, "null") == 0) {
-+ return NULL;
-+ }
-+ char *dstr = calloc(strlen(str) + 1, sizeof(char));
-+ char *pstr = dstr;
-+ for (; *str != 0; str++, pstr++) {
-+ if ((*str >= '0' && *str <= '9') || (*str >= 'A' && *str <= 'Z') || (*str >= 'a' && *str <= 'z')) {
-+ *pstr = *str;
-+ } else {
-+ *pstr = '_';
-+ }
-+ }
-+ *pstr = 0;
-+ return dstr;
-+}
-+
-+static int string_to_int(const char *str) {
-+ int x = 0;
-+ if (strlen(str) > 2 && str[0] == '0' && str[1] == 'x') {
-+ sscanf(str + 2, "%X", &x);
-+ } else {
-+ sscanf(str, "%d", &x);
-+ }
-+ return x;
-+}
-+
-+//
-+// Action list
-+//
-+
-+typedef bool (*DynosActionFunction)(const char *);
-+struct DynosAction {
-+ const char *str;
-+ DynosActionFunction action;
-+};
-+
-+// "Construct On First Use" aka COFU
-+static DA *dynos_get_action_list() {
-+ static DA sDynosActions = da_type(struct DynosAction);
-+ return &sDynosActions;
-+}
-+
-+static DynosActionFunction dynos_get_action(const char *funcName) {
-+ DA *dynosActions = dynos_get_action_list();
-+ for (int i = 0; i != dynosActions->count; ++i) {
-+ if (strcmp(da_getp(*dynosActions, i, struct DynosAction)->str, funcName) == 0) {
-+ return da_getp(*dynosActions, i, struct DynosAction)->action;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+void dynos_add_action(const char *funcName, bool (*funcPtr)(const char *), bool overwrite) {
-+ DA *dynosActions = dynos_get_action_list();
-+ for (int i = 0; i != dynosActions->count; ++i) {
-+ if (strcmp(da_getp(*dynosActions, i, struct DynosAction)->str, funcName) == 0) {
-+ if (overwrite) da_getp(*dynosActions, i, struct DynosAction)->action = funcPtr;
-+ return;
-+ }
-+ }
-+ struct DynosAction dynosAction = { make_copy(funcName, strlen(funcName) + 1), funcPtr };
-+ da_add(*dynosActions, dynosAction);
-+}
-+
-+//
-+// Utils
-+//
-+
-+static bool dynos_is_txt_file(const char *filename, unsigned int length) {
-+ return
-+ length >= 4 &&
-+ filename[length - 4] == '.' &&
-+ filename[length - 3] == 't' &&
-+ filename[length - 2] == 'x' &&
-+ filename[length - 1] == 't';
-+}
-+
-+//
-+// Tokenizer
-+//
-+
-+#define DYNOS_STR_TOKENS_MAX_LENGTH 64
-+#define DYNOS_STR_TOKENS_MAX_COUNT 128
-+#define DYNOS_LINE_MAX_LENGTH (DYNOS_STR_TOKENS_MAX_LENGTH * DYNOS_STR_TOKENS_MAX_COUNT)
-+
-+struct StrTokens {
-+ char tokens[DYNOS_STR_TOKENS_MAX_COUNT][DYNOS_STR_TOKENS_MAX_LENGTH];
-+ unsigned int count;
-+};
-+
-+static struct StrTokens dynos_split_string(const char *str) {
-+ struct StrTokens strtk = { .count = 0 };
-+ bool treatSpacesAsChar = false;
-+ for (int l = 0;; str++) {
-+ char c = *str;
-+ if (c == 0 || (c == ' ' && !treatSpacesAsChar) || c == '\t' || c == '\r' || c == '\n' || c == '#') {
-+ if (l > 0) {
-+ strtk.tokens[strtk.count][l] = 0;
-+ strtk.count++;
-+ if (strtk.count == DYNOS_STR_TOKENS_MAX_COUNT) {
-+ break;
-+ }
-+ l = 0;
-+ }
-+ if (c == 0 || c == '#') {
-+ break;
-+ }
-+ } else if (c == '\"') {
-+ treatSpacesAsChar = !treatSpacesAsChar;
-+ } else if (l < (DYNOS_STR_TOKENS_MAX_LENGTH - 1)) {
-+ strtk.tokens[strtk.count][l] = c;
-+ l++;
-+ }
-+ }
-+ return strtk;
-+}
-+
-+//
-+// Types and data
-+//
-+
-+enum DynosOptType {
-+ DOPT_TOGGLE,
-+ DOPT_CHOICE,
-+ DOPT_SCROLL,
-+ DOPT_BIND,
-+ DOPT_BUTTON,
-+ DOPT_SUBMENU,
-+
-+ // These ones are used by the Warp to Level built-in submenu
-+ DOPT_CHOICELEVEL,
-+ DOPT_CHOICESTAR,
-+};
-+
-+struct DynosOption {
-+ const char *name;
-+ const char *configName; // Name used in sm64config.txt
-+ const unsigned char *label;
-+ const unsigned char *label2; // Full caps label, displayed with colored font
-+ struct DynosOption *prev;
-+ struct DynosOption *next;
-+ struct DynosOption *parent;
-+ bool dynos; // true from create, false from convert
-+
-+ enum DynosOptType type;
-+ union {
-+
-+ // TOGGLE
-+ struct Toggle {
-+ bool *ptog;
-+ } toggle;
-+
-+ // CHOICE
-+ struct Choice {
-+ const unsigned char **choices;
-+ int count;
-+ int *pindex;
-+ } choice;
-+
-+ // SCROLL
-+ struct Scroll {
-+ int min;
-+ int max;
-+ int step;
-+ int *pvalue;
-+ } scroll;
-+
-+ // BIND
-+ struct Bind {
-+ unsigned int mask;
-+ unsigned int *pbinds;
-+ int index;
-+ } bind;
-+
-+ // BUTTON
-+ struct Button {
-+ const char *funcName;
-+ } button;
-+
-+ // SUBMENU
-+ struct Submenu {
-+ struct DynosOption *child;
-+ bool empty;
-+ } submenu;
-+ };
-+};
-+
-+static struct DynosOption *sPrevOpt = NULL;
-+static struct DynosOption *sDynosMenu = NULL;
-+static struct DynosOption *sOptionsMenu = NULL;
-+static const unsigned char *sDynosTextDynosMenu;
-+static const unsigned char *sDynosTextA;
-+static const unsigned char *sDynosTextOpenLeft;
-+static const unsigned char *sDynosTextCloseLeft;
-+#ifndef RENDER96_2_0
-+static const unsigned char *sDynosTextOptionsMenu;
-+static const unsigned char *sDynosTextDisabled;
-+static const unsigned char *sDynosTextEnabled;
-+static const unsigned char *sDynosTextNone;
-+static const unsigned char *sDynosTextDotDotDot;
-+static const unsigned char *sDynosTextOpenRight;
-+static const unsigned char *sDynosTextCloseRight;
-+#else
-+#define sDynosTextOptionsMenu r96str("TEXT_OPT_OPTIONS", false)
-+#define sDynosTextDisabled r96str("TEXT_OPT_DISABLED", true)
-+#define sDynosTextEnabled r96str("TEXT_OPT_ENABLED", true)
-+#define sDynosTextNone r96str("TEXT_OPT_UNBOUND", false)
-+#define sDynosTextDotDotDot r96str("TEXT_OPT_PRESSKEY", false)
-+#define sDynosTextOpenRight r96str("TEXT_OPT_BUTTON1", true)
-+#define sDynosTextCloseRight r96str("TEXT_OPT_BUTTON2", true)
-+#endif
-+
-+//
-+// Constructors
-+//
-+
-+static struct DynosOption *dynos_add_option(const char *name, const char *configName, const char *label, const char *label2) {
-+ struct DynosOption *opt = calloc(1, sizeof(struct DynosOption));
-+ opt->name = make_copy(name, strlen(name) + 1);
-+ opt->configName = string_to_configname(configName);
-+ opt->label = str64h(label);
-+ opt->label2 = str64h(label2);
-+ opt->dynos = true;
-+ if (sPrevOpt == NULL) { // The very first option
-+ opt->prev = NULL;
-+ opt->next = NULL;
-+ opt->parent = NULL;
-+ sDynosMenu = opt;
-+ } else {
-+ if (sPrevOpt->type == DOPT_SUBMENU && sPrevOpt->submenu.empty) { // First option of a sub-menu
-+ opt->prev = NULL;
-+ opt->next = NULL;
-+ opt->parent = sPrevOpt;
-+ sPrevOpt->submenu.child = opt;
-+ sPrevOpt->submenu.empty = false;
-+ } else {
-+ opt->prev = sPrevOpt;
-+ opt->next = NULL;
-+ opt->parent = sPrevOpt->parent;
-+ sPrevOpt->next = opt;
-+ }
-+ }
-+ sPrevOpt = opt;
-+ return opt;
-+}
-+
-+static void dynos_end_submenu() {
-+ if (sPrevOpt) {
-+ if (sPrevOpt->type == DOPT_SUBMENU && sPrevOpt->submenu.empty) { // ENDMENU command following a SUBMENU command
-+ sPrevOpt->submenu.empty = false;
-+ } else {
-+ sPrevOpt = sPrevOpt->parent;
-+ }
-+ }
-+}
-+
-+static void dynos_create_submenu(const char *name, const char *label, const char *label2) {
-+ struct DynosOption *opt = dynos_add_option(name, NULL, label, label2);
-+ opt->type = DOPT_SUBMENU;
-+ opt->submenu.child = NULL;
-+ opt->submenu.empty = true;
-+}
-+
-+static void dynos_create_toggle(const char *name, const char *configName, const char *label, int initValue) {
-+ struct DynosOption *opt = dynos_add_option(name, configName, label, label);
-+ opt->type = DOPT_TOGGLE;
-+ opt->toggle.ptog = calloc(1, sizeof(bool));
-+ *opt->toggle.ptog = (unsigned char) initValue;
-+}
-+
-+static void dynos_create_scroll(const char *name, const char *configName, const char *label, int min, int max, int step, int initValue) {
-+ struct DynosOption *opt = dynos_add_option(name, configName, label, label);
-+ opt->type = DOPT_SCROLL;
-+ opt->scroll.min = min;
-+ opt->scroll.max = max;
-+ opt->scroll.step = step;
-+ opt->scroll.pvalue = calloc(1, sizeof(int));
-+ *opt->scroll.pvalue = initValue;
-+}
-+
-+static void dynos_create_choice(const char *name, const char *configName, const char *label, const char **choices, int count, int initValue) {
-+ struct DynosOption *opt = dynos_add_option(name, configName, label, label);
-+ opt->type = DOPT_CHOICE;
-+ opt->choice.choices = calloc(count, sizeof(const unsigned char *));
-+ for (int i = 0; i != count; ++i) {
-+ opt->choice.choices[i] = str64h(choices[i]);
-+ }
-+ opt->choice.count = count;
-+ opt->choice.pindex = calloc(1, sizeof(int));
-+ *opt->choice.pindex = initValue;
-+}
-+
-+static void dynos_create_button(const char *name, const char *label, const char *funcName) {
-+ struct DynosOption *opt = dynos_add_option(name, NULL, label, label);
-+ opt->type = DOPT_BUTTON;
-+ opt->button.funcName = make_copy(funcName, strlen(funcName) + 1);
-+}
-+
-+static void dynos_create_bind(const char *name, const char *configName, const char *label, unsigned int mask, unsigned int *binds) {
-+ struct DynosOption *opt = dynos_add_option(name, configName, label, label);
-+ opt->type = DOPT_BIND;
-+ opt->bind.mask = mask;
-+ opt->bind.pbinds = calloc(MAX_BINDS, sizeof(unsigned int));
-+ for (int i = 0; i != MAX_BINDS; ++i) {
-+ opt->bind.pbinds[i] = binds[i];
-+ }
-+ opt->bind.index = 0;
-+}
-+
-+static void dynos_read_file(const char *folder, const char *filename) {
-+
-+ // Open file
-+ char fullname[SYS_MAX_PATH];
-+ snprintf(fullname, SYS_MAX_PATH, "%s/%s", folder, filename);
-+ FILE *f = fopen(fullname, "rt");
-+ if (f == NULL) {
-+ return;
-+ }
-+
-+ // Read file and create options
-+ char buffer[DYNOS_LINE_MAX_LENGTH];
-+ while (fgets(buffer, DYNOS_LINE_MAX_LENGTH, f) != NULL) {
-+ struct StrTokens strtk = dynos_split_string(buffer);
-+
-+ // Empty line
-+ if (strtk.count == 0) {
-+ continue;
-+ }
-+
-+ // SUBMENU [Name] [Label]
-+ if (strcmp(strtk.tokens[0], "SUBMENU") == 0 && strtk.count >= 4) {
-+ dynos_create_submenu(strtk.tokens[1], strtk.tokens[2], strtk.tokens[3]);
-+ continue;
-+ }
-+
-+ // TOGGLE [Name] [Label] [ConfigName] [InitialValue]
-+ if (strcmp(strtk.tokens[0], "TOGGLE") == 0 && strtk.count >= 5) {
-+ dynos_create_toggle(strtk.tokens[1], strtk.tokens[3], strtk.tokens[2], string_to_int(strtk.tokens[4]));
-+ continue;
-+ }
-+
-+ // SCROLL [Name] [Label] [ConfigName] [InitialValue] [Min] [Max] [Step]
-+ if (strcmp(strtk.tokens[0], "SCROLL") == 0 && strtk.count >= 8) {
-+ dynos_create_scroll(strtk.tokens[1], strtk.tokens[3], strtk.tokens[2], string_to_int(strtk.tokens[5]), string_to_int(strtk.tokens[6]), string_to_int(strtk.tokens[7]), string_to_int(strtk.tokens[4]));
-+ continue;
-+ }
-+
-+ // CHOICE [Name] [Label] [ConfigName] [InitialValue] [ChoiceStrings...]
-+ if (strcmp(strtk.tokens[0], "CHOICE") == 0 && strtk.count >= 6) {
-+ const char *choices[DYNOS_STR_TOKENS_MAX_COUNT];
-+ for (unsigned int i = 0; i != strtk.count - 5; ++i) {
-+ choices[i] = &strtk.tokens[5 + i][0];
-+ }
-+ dynos_create_choice(strtk.tokens[1], strtk.tokens[3], strtk.tokens[2], choices, strtk.count - 5, string_to_int(strtk.tokens[4]));
-+ continue;
-+ }
-+
-+ // BUTTON [Name] [Label] [FuncName]
-+ if (strcmp(strtk.tokens[0], "BUTTON") == 0 && strtk.count >= 4) {
-+ dynos_create_button(strtk.tokens[1], strtk.tokens[2], strtk.tokens[3]);
-+ continue;
-+ }
-+
-+ // BIND [Name] [Label] [ConfigName] [Mask] [DefaultValues]
-+ if (strcmp(strtk.tokens[0], "BIND") == 0 && strtk.count >= 5 + MAX_BINDS) {
-+ unsigned int binds[MAX_BINDS];
-+ for (int i = 0; i != MAX_BINDS; ++i) {
-+ binds[i] = string_to_int(strtk.tokens[5 + i]);
-+ }
-+ dynos_create_bind(strtk.tokens[1], strtk.tokens[3], strtk.tokens[2], string_to_int(strtk.tokens[4]), binds);
-+ continue;
-+ }
-+
-+ // ENDMENU
-+ if (strcmp(strtk.tokens[0], "ENDMENU") == 0) {
-+ dynos_end_submenu();
-+ continue;
-+ }
-+ }
-+ fclose(f);
-+}
-+
-+static void dynos_load_options() {
-+ char optionsFolder[SYS_MAX_PATH];
-+ snprintf(optionsFolder, SYS_MAX_PATH, "%s/%s", sys_exe_path(), FS_BASEDIR);
-+ DIR *dir = opendir(optionsFolder);
-+ sPrevOpt = NULL;
-+ if (dir) {
-+ struct dirent *ent = NULL;
-+ while ((ent = readdir(dir)) != NULL) {
-+ if (dynos_is_txt_file(ent->d_name, strlen(ent->d_name))) {
-+ dynos_read_file(optionsFolder, ent->d_name);
-+ }
-+ }
-+ closedir(dir);
-+ }
-+}
-+
-+//
-+// Vanilla options menu
-+//
-+
-+// Not my problem
-+#pragma GCC diagnostic push
-+#pragma GCC diagnostic ignored "-Wsizeof-pointer-div"
-+#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers"
-+#pragma GCC diagnostic ignored "-Wpointer-sign"
-+#pragma GCC diagnostic ignored "-Wsign-compare"
-+#define optmenu_toggle optmenu_toggle_unused
-+#define optmenu_draw optmenu_draw_unused
-+#define optmenu_draw_prompt optmenu_draw_prompt_unused
-+#define optmenu_check_buttons optmenu_check_buttons_unused
-+#define optmenu_open optmenu_open_unused
-+#define DYNOS_INL
-+#include "game/options_menu.c"
-+#undef DYNOS_INL
-+#undef optmenu_toggle
-+#undef optmenu_draw
-+#undef optmenu_draw_prompt
-+#undef optmenu_check_buttons
-+#undef optmenu_open
-+#pragma GCC diagnostic pop
-+// Now, that's my problem
-+
-+//
-+// Vanilla actions
-+//
-+
-+typedef void (*VanillaActionFunction)(struct Option *, int);
-+struct VanillaAction {
-+ const char *str;
-+ VanillaActionFunction action;
-+};
-+
-+// "Construct On First Use" aka COFU
-+static DA *dynos_get_vanilla_action_list() {
-+ static DA sVanillaActions = da_type(struct VanillaAction);
-+ return &sVanillaActions;
-+}
-+
-+static VanillaActionFunction dynos_get_vanilla_action(const char *name) {
-+ DA *vanillaActions = dynos_get_vanilla_action_list();
-+ for (int i = 0; i != vanillaActions->count; ++i) {
-+ if (strcmp(da_getp(*vanillaActions, i, struct VanillaAction)->str, name) == 0) {
-+ return da_getp(*vanillaActions, i, struct VanillaAction)->action;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static void dynos_add_vanilla_action(const char *name, VanillaActionFunction func) {
-+ DA *vanillaActions = dynos_get_vanilla_action_list();
-+ for (int i = 0; i != vanillaActions->count; ++i) {
-+ if (strcmp(da_getp(*vanillaActions, i, struct VanillaAction)->str, name) == 0) {
-+ return;
-+ }
-+ }
-+ struct VanillaAction vanillaAction = { name, func };
-+ da_add(*vanillaActions, vanillaAction);
-+}
-+
-+static bool dynos_call_vanilla_action(const char *optName) {
-+ VanillaActionFunction func = dynos_get_vanilla_action(optName);
-+ if (func) {
-+ func(NULL, 0);
-+ return true;
-+ }
-+ return false;
-+}
-+
-+//
-+// Convert classic options menu into DynOS menu
-+//
-+
-+static struct DynosOption *dynos_convert_option(const unsigned char *label, const unsigned char *label2) {
-+ struct DynosOption *opt = calloc(1, sizeof(struct DynosOption));
-+ opt->name = (const char *) label;
-+ opt->configName = NULL;
-+#ifdef RENDER96_2_0
-+ opt->label = label;
-+#else
-+ opt->label = str64d(make_copy(label, str64l(label) + 1));
-+#endif
-+ opt->label2 = label2;
-+ opt->dynos = false;
-+ if (sPrevOpt == NULL) { // The very first option
-+ opt->prev = NULL;
-+ opt->next = NULL;
-+ opt->parent = NULL;
-+ sOptionsMenu = opt;
-+ } else {
-+ if (sPrevOpt->type == DOPT_SUBMENU && sPrevOpt->submenu.empty) { // First option of a sub-menu
-+ opt->prev = NULL;
-+ opt->next = NULL;
-+ opt->parent = sPrevOpt;
-+ sPrevOpt->submenu.child = opt;
-+ sPrevOpt->submenu.empty = false;
-+ } else {
-+ opt->prev = sPrevOpt;
-+ opt->next = NULL;
-+ opt->parent = sPrevOpt->parent;
-+ sPrevOpt->next = opt;
-+ }
-+ }
-+ sPrevOpt = opt;
-+ return opt;
-+}
-+
-+static void dynos_convert_submenu(const unsigned char *label, const unsigned char *label2) {
-+ struct DynosOption *opt = dynos_convert_option(label, label2);
-+ opt->type = DOPT_SUBMENU;
-+ opt->submenu.child = NULL;
-+ opt->submenu.empty = true;
-+}
-+
-+static void dynos_convert_toggle(const unsigned char *label, bool *bval) {
-+ struct DynosOption *opt = dynos_convert_option(label, label);
-+ opt->type = DOPT_TOGGLE;
-+ opt->toggle.ptog = (bool *) bval;
-+}
-+
-+static void dynos_convert_scroll(const unsigned char *label, int min, int max, int step, unsigned int *uval) {
-+ struct DynosOption *opt = dynos_convert_option(label, label);
-+ opt->type = DOPT_SCROLL;
-+ opt->scroll.min = min;
-+ opt->scroll.max = max;
-+ opt->scroll.step = step;
-+ opt->scroll.pvalue = (int *) uval;
-+}
-+
-+static void dynos_convert_choice(const unsigned char *label, const unsigned char **choices, int count, unsigned int *uval) {
-+ struct DynosOption *opt = dynos_convert_option(label, label);
-+ opt->type = DOPT_CHOICE;
-+ opt->choice.choices = choices;
-+ opt->choice.count = count;
-+ opt->choice.pindex = (int *) uval;
-+}
-+
-+static void dynos_convert_button(const unsigned char *label, VanillaActionFunction action) {
-+ struct DynosOption *opt = dynos_convert_option(label, label);
-+ opt->type = DOPT_BUTTON;
-+ opt->button.funcName = make_copy("dynos_call_vanilla_action", 26);
-+ dynos_add_vanilla_action(opt->name, action);
-+}
-+
-+static void dynos_convert_bind(const unsigned char *label, unsigned int *binds) {
-+ struct DynosOption *opt = dynos_convert_option(label, label);
-+ opt->type = DOPT_BIND;
-+ opt->bind.mask = 0;
-+ opt->bind.pbinds = binds;
-+ opt->bind.index = 0;
-+}
-+
-+static void dynos_convert_submenu_options(struct SubMenu *submenu) {
-+ for (int i = 0; i != submenu->numOpts; ++i) {
-+ struct Option *opt = &submenu->opts[i];
-+ switch (opt->type) {
-+ case OPT_TOGGLE:
-+ dynos_convert_toggle(opt->label, opt->bval);
-+ break;
-+
-+ case OPT_CHOICE:
-+ dynos_convert_choice(opt->label, opt->choices, opt->numChoices, opt->uval);
-+ break;
-+
-+ case OPT_SCROLL:
-+ dynos_convert_scroll(opt->label, opt->scrMin, opt->scrMax, opt->scrStep, opt->uval);
-+ break;
-+
-+ case OPT_SUBMENU:
-+ dynos_convert_submenu(opt->label, opt->nextMenu->label);
-+ dynos_convert_submenu_options(opt->nextMenu);
-+ dynos_end_submenu();
-+ break;
-+
-+ case OPT_BIND:
-+ dynos_convert_bind(opt->label, opt->uval);
-+ break;
-+
-+ case OPT_BUTTON:
-+ dynos_convert_button(opt->label, opt->actionFn);
-+ break;
-+
-+ default:
-+ break;
-+ }
-+ }
-+}
-+
-+static void dynos_convert_options_menu() {
-+ sPrevOpt = NULL;
-+ dynos_convert_submenu_options(&menuMain);
-+ dynos_add_action("dynos_call_vanilla_action", dynos_call_vanilla_action, true);
-+}
-+
-+//
-+// Loop through DynosOptions
-+//
-+
-+typedef bool (*DynosLoopFunc)(struct DynosOption *, void *);
-+static struct DynosOption *dynos_loop(struct DynosOption *opt, DynosLoopFunc func, void *data) {
-+ while (opt) {
-+ if (opt->type == DOPT_SUBMENU) {
-+ struct DynosOption *res = dynos_loop(opt->submenu.child, func, data);
-+ if (res) {
-+ return res;
-+ }
-+ } else {
-+ if (func(opt, data)) {
-+ return opt;
-+ }
-+ }
-+ opt = opt->next;
-+ }
-+ return NULL;
-+}
-+
-+//
-+// Controllers
-+//
-+
-+static bool dynos_controller_is_key_down(int i, int k) {
-+
-+ // Keyboard
-+ if (i == 0 && k >= 0 && k < SDL_NUM_SCANCODES) {
-+ return SDL_GetKeyboardState(NULL)[k];
-+ }
-+
-+ // Game Controller
-+ else if (k >= 0x1000) {
-+
-+ // Button
-+ int b = (k - 0x1000);
-+ if (b < SDL_CONTROLLER_BUTTON_MAX) {
-+ return SDL_GameControllerGetButton(SDL_GameControllerOpen(i - 1), b);
-+ }
-+
-+ // Axis
-+ int a = (k - 0x1000 - SDL_CONTROLLER_BUTTON_MAX);
-+ if (a < SDL_CONTROLLER_AXIS_MAX * 2) {
-+ int axis = SDL_GameControllerGetAxis(SDL_GameControllerOpen(i - 1), a / 2);
-+ if (a & 1) return (axis < SDL_JOYSTICK_AXIS_MIN / 2);
-+ else return (axis > SDL_JOYSTICK_AXIS_MAX / 2);
-+ }
-+ }
-+
-+ // Invalid
-+ return false;
-+}
-+
-+#define MAX_CONTS 8
-+static bool dynos_controller_update(struct DynosOption *opt, void *data) {
-+ if (opt->type == DOPT_BIND) {
-+ OSContPad *pad = (OSContPad *) data;
-+ for (int i = 0; i < MAX_CONTS; ++i)
-+ for (int j = 0; j < MAX_BINDS; ++j) {
-+ pad->button |= opt->bind.mask * dynos_controller_is_key_down(i, opt->bind.pbinds[j]);
-+ }
-+ }
-+ return false;
-+}
-+
-+#define MAX_GKEYS (SDL_CONTROLLER_BUTTON_MAX + SDL_CONTROLLER_AXIS_MAX * 2)
-+static int sBindingState = 0; // 0 = No bind, 1 = Wait for all keys released, 2 = Return first pressed key
-+static int dynos_controller_get_key_pressed() {
-+
-+ // Keyboard
-+ for (int k = 0; k < SDL_NUM_SCANCODES; ++k) {
-+ if (dynos_controller_is_key_down(0, k)) {
-+ if (sBindingState == 1) return VK_INVALID;
-+ return k;
-+ }
-+ }
-+
-+ // Game Controller
-+ for (int i = 1; i < MAX_CONTS; ++i)
-+ for (int k = 0; k < MAX_GKEYS; ++k) {
-+ if (dynos_controller_is_key_down(i, k + 0x1000)) {
-+ if (sBindingState == 1) return VK_INVALID;
-+ return k + 0x1000;
-+ }
-+ }
-+
-+ // No key
-+ sBindingState = 2;
-+ return VK_INVALID;
-+}
-+
-+//
-+// Config
-+//
-+
-+static const char *sDynosConfigFilename = "DynOSconfig.cfg";
-+
-+static bool dynos_get_option_config(struct DynosOption *opt, void *data) {
-+ unsigned char type = *(unsigned char *) ((void **) data)[0];
-+ const char *name = (const char *) ((void **) data)[1];
-+ return (opt->configName != NULL && strcmp(opt->configName, name) == 0 && opt->type == type);
-+}
-+
-+static bool dynos_read_config_type_and_name(FILE *f, unsigned char *type, char *name) {
-+
-+ // Read type
-+ if (fread(type, sizeof(unsigned char), 1, f) != 1) {
-+ return false;
-+ }
-+
-+ // Read string length
-+ unsigned char len = 0;
-+ if (fread(&len, sizeof(unsigned char), 1, f) != 1) {
-+ return false;
-+ }
-+
-+ // Read config name
-+ if (fread(name, sizeof(char), len, f) != len) {
-+ return false;
-+ }
-+ name[len] = 0;
-+ return true;
-+}
-+
-+static void dynos_load_config() {
-+ char filename[SYS_MAX_PATH];
-+ snprintf(filename, SYS_MAX_PATH, "%s/%s", sys_user_path(), sDynosConfigFilename);
-+ FILE *f = fopen(filename, "rb");
-+ if (f == NULL) return;
-+ while (true) {
-+ unsigned char type;
-+ char name[DYNOS_STR_TOKENS_MAX_LENGTH];
-+ if (!dynos_read_config_type_and_name(f, &type, name)) {
-+ break;
-+ }
-+
-+ struct DynosOption *opt = dynos_loop(sDynosMenu, dynos_get_option_config, (void **) (void *[]){ &type, name });
-+ if (opt != NULL) {
-+ switch (opt->type) {
-+ case DOPT_TOGGLE:
-+ fread(opt->toggle.ptog, sizeof(bool), 1, f);
-+ break;
-+
-+ case DOPT_CHOICE:
-+ fread(opt->choice.pindex, sizeof(int), 1, f);
-+ break;
-+
-+ case DOPT_SCROLL:
-+ fread(opt->scroll.pvalue, sizeof(int), 1, f);
-+ break;
-+
-+ case DOPT_BIND:
-+ fread(opt->bind.pbinds, sizeof(unsigned int), MAX_BINDS, f);
-+ break;
-+
-+ default:
-+ break;
-+ }
-+ }
-+ }
-+ fclose(f);
-+}
-+
-+static void dynos_write_config_type_and_name(FILE *f, unsigned char type, const char *name) {
-+ unsigned char len = (unsigned char) strlen(name);
-+ fwrite(&type, sizeof(unsigned char), 1, f);
-+ fwrite(&len, sizeof(unsigned char), 1, f);
-+ fwrite(name, sizeof(char), len, f);
-+}
-+
-+static bool dynos_save_option_config(struct DynosOption *opt, void *data) {
-+ if (opt->configName != NULL) {
-+ FILE *f = (FILE *) data;
-+ switch (opt->type) {
-+ case DOPT_TOGGLE:
-+ dynos_write_config_type_and_name(f, DOPT_TOGGLE, opt->configName);
-+ fwrite(opt->toggle.ptog, sizeof(bool), 1, f);
-+ break;
-+
-+ case DOPT_CHOICE:
-+ dynos_write_config_type_and_name(f, DOPT_CHOICE, opt->configName);
-+ fwrite(opt->choice.pindex, sizeof(int), 1, f);
-+ break;
-+
-+ case DOPT_SCROLL:
-+ dynos_write_config_type_and_name(f, DOPT_SCROLL, opt->configName);
-+ fwrite(opt->scroll.pvalue, sizeof(int), 1, f);
-+ break;
-+
-+ case DOPT_BIND:
-+ dynos_write_config_type_and_name(f, DOPT_BIND, opt->configName);
-+ fwrite(opt->bind.pbinds, sizeof(unsigned int), MAX_BINDS, f);
-+ break;
-+
-+ default:
-+ break;
-+ }
-+ }
-+ return 0;
-+}
-+
-+static void dynos_save_config() {
-+ char filename[SYS_MAX_PATH];
-+ snprintf(filename, SYS_MAX_PATH, "%s/%s", sys_user_path(), sDynosConfigFilename);
-+ FILE *f = fopen(filename, "wb");
-+ if (f == NULL) return;
-+ dynos_loop(sDynosMenu, dynos_save_option_config, (void *) f);
-+ fclose(f);
-+}
-+
-+//
-+// Get/Set values
-+//
-+
-+static bool dynos_get(struct DynosOption *opt, void *data) {
-+ return (strcmp(opt->name, (const char *) data) == 0);
-+}
-+
-+int dynos_get_value(const char *name) {
-+ struct DynosOption *opt = dynos_loop(sDynosMenu, dynos_get, (void *) name);
-+ if (opt) {
-+ switch (opt->type) {
-+ case DOPT_TOGGLE: return (int) *opt->toggle.ptog;
-+ case DOPT_CHOICE: return *opt->choice.pindex;
-+ case DOPT_CHOICELEVEL: return *opt->choice.pindex;
-+ case DOPT_CHOICESTAR: return *opt->choice.pindex;
-+ case DOPT_SCROLL: return *opt->scroll.pvalue;
-+ default: break;
-+ }
-+ }
-+ return 0;
-+}
-+
-+void dynos_set_value(const char *name, int value) {
-+ struct DynosOption *opt = dynos_loop(sDynosMenu, dynos_get, (void *) name);
-+ if (opt) {
-+ switch (opt->type) {
-+ case DOPT_TOGGLE: *opt->toggle.ptog = (bool) value; break;
-+ case DOPT_CHOICE: *opt->choice.pindex = value; break;
-+ case DOPT_CHOICELEVEL: *opt->choice.pindex = value; break;
-+ case DOPT_CHOICESTAR: *opt->choice.pindex = value; break;
-+ case DOPT_SCROLL: *opt->scroll.pvalue = value; break;
-+ default: break;
-+ }
-+ }
-+}
-+
-+//
-+// Render
-+//
-+
-+static struct DynosOption *sCurrentMenu = NULL;
-+static struct DynosOption *sCurrentOpt = NULL;
-+
-+static int dynos_get_render_string_length(const unsigned char *str64) {
-+ int length = 0;
-+ for (; *str64 != DIALOG_CHAR_TERMINATOR; ++str64) {
-+ length += str64w(*str64);
-+ }
-+ return length;
-+}
-+
-+static void dynos_render_string(const unsigned char *str64, int x, int y) {
-+ create_dl_translation_matrix(MENU_MTX_PUSH, x, y, 0);
-+ for (; *str64 != DIALOG_CHAR_TERMINATOR; ++str64) {
-+ if (*str64 != DIALOG_CHAR_SPACE) {
-+ if (*str64 == 253) { // underscore
-+ create_dl_translation_matrix(MENU_MTX_NOPUSH, -1, -5, 0);
-+ void **fontLUT = (void **) segmented_to_virtual(main_font_lut);
-+ void *packedTexture = segmented_to_virtual(fontLUT[159]);
-+ gDPPipeSync(gDisplayListHead++);
-+ gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, VIRTUAL_TO_PHYSICAL(packedTexture));
-+ gSPDisplayList(gDisplayListHead++, dl_ia_text_tex_settings);
-+ create_dl_translation_matrix(MENU_MTX_NOPUSH, 0, +5, 0);
-+ } else {
-+ void **fontLUT = (void **) segmented_to_virtual(main_font_lut);
-+ void *packedTexture = segmented_to_virtual(fontLUT[*str64]);
-+ gDPPipeSync(gDisplayListHead++);
-+ gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, VIRTUAL_TO_PHYSICAL(packedTexture));
-+ gSPDisplayList(gDisplayListHead++, dl_ia_text_tex_settings);
-+ }
-+ }
-+ create_dl_translation_matrix(MENU_MTX_NOPUSH, str64w(*str64), 0, 0);
-+ }
-+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
-+}
-+
-+static void dynos_print_string(const unsigned char *str64, int x, int y, unsigned int rgbaFront, unsigned int rgbaBack, bool alignLeft) {
-+ gSPDisplayList(gDisplayListHead++, dl_ia_text_begin);
-+ if ((rgbaBack & 0xFF) != 0) {
-+ gDPSetEnvColor(gDisplayListHead++, ((rgbaBack >> 24) & 0xFF), ((rgbaBack >> 16) & 0xFF), ((rgbaBack >> 8) & 0xFF), ((rgbaBack >> 0) & 0xFF));
-+ if (alignLeft) {
-+ dynos_render_string(str64, GFX_DIMENSIONS_FROM_LEFT_EDGE(x) + 1, y - 1);
-+ } else {
-+ dynos_render_string(str64, GFX_DIMENSIONS_FROM_RIGHT_EDGE(x + dynos_get_render_string_length(str64) - 1), y - 1);
-+ }
-+ }
-+ if ((rgbaFront & 0xFF) != 0) {
-+ gDPSetEnvColor(gDisplayListHead++, ((rgbaFront >> 24) & 0xFF), ((rgbaFront >> 16) & 0xFF), ((rgbaFront >> 8) & 0xFF), ((rgbaFront >> 0) & 0xFF));
-+ if (alignLeft) {
-+ dynos_render_string(str64, GFX_DIMENSIONS_FROM_LEFT_EDGE(x), y);
-+ } else {
-+ dynos_render_string(str64, GFX_DIMENSIONS_FROM_RIGHT_EDGE(x + dynos_get_render_string_length(str64)), y);
-+ }
-+ }
-+ gSPDisplayList(gDisplayListHead++, dl_ia_text_end);
-+ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255);
-+}
-+
-+static void dynos_print_box(int x, int y, int w, int h, unsigned int rgbaColor, bool alignLeft) {
-+ if ((rgbaColor && 0xFF) != 0) {
-+ Mtx *matrix = (Mtx *) alloc_display_list(sizeof(Mtx));
-+ if (!matrix) return;
-+ if (alignLeft) {
-+ create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(x), y + h, 0);
-+ } else {
-+ create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_RIGHT_EDGE(x + w), y + h, 0);
-+ }
-+ guScale(matrix, (float) w / 130.f, (float) h / 80.f, 1.f);
-+ gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH);
-+ gDPSetEnvColor(gDisplayListHead++, ((rgbaColor >> 24) & 0xFF), ((rgbaColor >> 16) & 0xFF), ((rgbaColor >> 8) & 0xFF), ((rgbaColor >> 0) & 0xFF));
-+ gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box);
-+ gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW);
-+ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255);
-+ }
-+}
-+
-+static inline int dynos_get_label2_x(int x, const unsigned char *str) {
-+ int len = 0;
-+ while (*str != GLOBAR_CHAR_TERMINATOR) ++str, ++len;
-+ return x - len * 6; // stride is 12
-+}
-+
-+//
-+// Options menu
-+//
-+
-+#ifdef RENDER96_2_0
-+static const unsigned char *r96lang(struct DynosOption *opt) {
-+ static unsigned char buffer[DYNOS_STR_TOKENS_MAX_LENGTH];
-+ unsigned char *s = getTranslatedText(languages[*opt->choice.pindex]->name);
-+ memcpy(buffer, s, str64l(s) + 1);
-+ free(s);
-+ opt->choice.count = languagesAmount;
-+ return str64d(buffer);
-+}
-+
-+static const unsigned char *r96str(const char *str64, bool decaps) {
-+ static unsigned char buffer[DYNOS_STR_TOKENS_MAX_LENGTH];
-+ unsigned char *s = get_key_string((char *) str64);
-+ memcpy(buffer, s, str64l(s) + 1);
-+ return (decaps ? str64d(buffer) : buffer);
-+}
-+
-+#define get_label(opt) (opt->dynos ? opt->label : r96str((const char *) opt->label, true))
-+#define get_label2(opt) (opt->dynos ? opt->label2 : r96str((const char *) opt->label2, false))
-+#define get_choice(opt) (opt->dynos ? opt->choice.choices[*opt->choice.pindex] : (strcmp((const char *) opt->label, (const char *) optsGameStr[0]) == 0 ? r96lang(opt) : r96str((const char *) opt->choice.choices[*opt->choice.pindex], true)))
-+#else
-+#define get_label(opt) (opt->label)
-+#define get_label2(opt) (opt->label2)
-+#define get_choice(opt) (opt->choice.choices[*opt->choice.pindex])
-+#endif
-+
-+static int dynos_get_option_count() {
-+ struct DynosOption *opt = sCurrentOpt;
-+ while (opt->prev) {
-+ opt = opt->prev;
-+ }
-+ int count = 0;
-+ while (opt) {
-+ opt = opt->next;
-+ count++;
-+ }
-+ return count;
-+}
-+
-+static int dynos_get_current_option_index() {
-+ struct DynosOption *opt = sCurrentOpt;
-+ int index = 0;
-+ while (opt->prev) {
-+ opt = opt->prev;
-+ index++;
-+ }
-+ return index;
-+}
-+
-+#define PREV(opt) (opt == NULL ? NULL : opt->prev)
-+#define NEXT(opt) (opt == NULL ? NULL : opt->next)
-+static struct DynosOption **dynos_get_options_to_draw() {
-+ static struct DynosOption *sOptionList[13];
-+ bzero(sOptionList, 13 * sizeof(struct DynosOption *));
-+
-+ sOptionList[6] = sCurrentOpt;
-+ sOptionList[5] = PREV(sOptionList[6]);
-+ sOptionList[4] = PREV(sOptionList[5]);
-+ sOptionList[3] = PREV(sOptionList[4]);
-+ sOptionList[2] = PREV(sOptionList[3]);
-+ sOptionList[1] = PREV(sOptionList[2]);
-+ sOptionList[0] = PREV(sOptionList[1]);
-+ sOptionList[7] = NEXT(sOptionList[6]);
-+ sOptionList[8] = NEXT(sOptionList[7]);
-+ sOptionList[9] = NEXT(sOptionList[8]);
-+ sOptionList[10] = NEXT(sOptionList[9]);
-+ sOptionList[11] = NEXT(sOptionList[10]);
-+ sOptionList[12] = NEXT(sOptionList[11]);
-+
-+ int start = 12, end = 0;
-+ for (int i = 0; i != 13; ++i) {
-+ if (sOptionList[i] != NULL) {
-+ start = MIN(start, i);
-+ end = MAX(end, i);
-+ }
-+ }
-+
-+ if (end - start < 7) {
-+ return &sOptionList[start];
-+ }
-+ if (end <= 9) {
-+ return &sOptionList[end - 6];
-+ }
-+ if (start >= 3) {
-+ return &sOptionList[start];
-+ }
-+ return &sOptionList[3];
-+}
-+#undef PREV
-+#undef NEXT
-+
-+#define COLOR_WHITE 0xFFFFFFFF
-+#define COLOR_BLACK 0x000000FF
-+#define COLOR_GRAY 0xA0A0A0FF
-+#define COLOR_DARK_GRAY 0x808080FF
-+#define COLOR_SELECT 0x80E0FFFF
-+#define COLOR_SELECT_BOX 0x00FFFF20
-+#define COLOR_ENABLED 0x20E020FF
-+#define COLOR_DISABLED 0xFF2020FF
-+#define OFFSET_FROM_LEFT_EDGE (20.f * sqr(GFX_DIMENSIONS_ASPECT_RATIO))
-+#define OFFSET_FROM_RIGHT_EDGE (20.f * sqr(GFX_DIMENSIONS_ASPECT_RATIO))
-+#define SCROLL_BAR_SIZE ((int) (45.f * GFX_DIMENSIONS_ASPECT_RATIO))
-+
-+static void dynos_draw_option(struct DynosOption *opt, int y) {
-+ if (opt == NULL) {
-+ return;
-+ }
-+
-+ // Selected box
-+ if (opt == sCurrentOpt) {
-+ unsigned char a = (unsigned char) ((coss(gGlobalTimer * 0x800) + 1.f) * 0x20);
-+ dynos_print_box(OFFSET_FROM_LEFT_EDGE - 4, y - 2, GFX_DIMENSIONS_FROM_RIGHT_EDGE(OFFSET_FROM_RIGHT_EDGE) - GFX_DIMENSIONS_FROM_LEFT_EDGE(OFFSET_FROM_LEFT_EDGE) + 8, 20, COLOR_SELECT_BOX + a, 1);
-+ }
-+
-+ // Label
-+ if (opt == sCurrentOpt) {
-+ dynos_print_string(get_label(opt), OFFSET_FROM_LEFT_EDGE, y, COLOR_SELECT, COLOR_BLACK, 1);
-+ } else {
-+ dynos_print_string(get_label(opt), OFFSET_FROM_LEFT_EDGE, y, COLOR_WHITE, COLOR_BLACK, 1);
-+ }
-+
-+ // Values
-+ int w;
-+ switch (opt->type) {
-+ case DOPT_TOGGLE:
-+ if (*opt->toggle.ptog) {
-+ dynos_print_string(sDynosTextEnabled, OFFSET_FROM_RIGHT_EDGE, y, COLOR_ENABLED, COLOR_BLACK, 0);
-+ } else {
-+ dynos_print_string(sDynosTextDisabled, OFFSET_FROM_RIGHT_EDGE, y, COLOR_DISABLED, COLOR_BLACK, 0);
-+ }
-+ break;
-+
-+ case DOPT_CHOICE:
-+ dynos_print_string(get_choice(opt), OFFSET_FROM_RIGHT_EDGE, y, opt == sCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0);
-+ break;
-+
-+ case DOPT_CHOICELEVEL:
-+ dynos_print_string(level_get_name(level_get_list(true, true)[*opt->choice.pindex], true, true), OFFSET_FROM_RIGHT_EDGE, y, opt == sCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0);
-+ break;
-+
-+ case DOPT_CHOICESTAR:
-+ dynos_print_string(level_get_star_name(level_get_list(true, true)[dynos_get_value("dynos_warp_level")], *opt->choice.pindex + 1, true, true), OFFSET_FROM_RIGHT_EDGE, y, opt == sCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0);
-+ break;
-+
-+ case DOPT_SCROLL:
-+ w = (int) (SCROLL_BAR_SIZE * (float) (*opt->scroll.pvalue - opt->scroll.min) / (float) (opt->scroll.max - opt->scroll.min));
-+ dynos_print_string(str64s("%d", *opt->scroll.pvalue), OFFSET_FROM_RIGHT_EDGE, y, opt == sCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0);
-+ dynos_print_box(OFFSET_FROM_RIGHT_EDGE + 28, y + 4, SCROLL_BAR_SIZE + 2, 8, COLOR_DARK_GRAY, 0);
-+ dynos_print_box(OFFSET_FROM_RIGHT_EDGE + 29 + SCROLL_BAR_SIZE - w, y + 5, w, 6, opt == sCurrentOpt ? COLOR_SELECT : COLOR_WHITE, 0);
-+ break;
-+
-+ case DOPT_BIND:
-+ for (int i = 0; i != MAX_BINDS; ++i) {
-+ unsigned int bind = opt->bind.pbinds[i];
-+ if (opt == sCurrentOpt && i == opt->bind.index) {
-+ if (sBindingState != 0) {
-+ dynos_print_string(sDynosTextDotDotDot, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, y, COLOR_SELECT, COLOR_BLACK, 0);
-+ } else if (bind == VK_INVALID) {
-+ dynos_print_string(sDynosTextNone, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, y, COLOR_SELECT, COLOR_BLACK, 0);
-+ } else {
-+ dynos_print_string(str64s("%04X", bind), OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, y, COLOR_SELECT, COLOR_BLACK, 0);
-+ }
-+ } else {
-+ if (bind == VK_INVALID) {
-+ dynos_print_string(sDynosTextNone, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, y, COLOR_GRAY, COLOR_BLACK, 0);
-+ } else {
-+ dynos_print_string(str64s("%04X", bind), OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, y, COLOR_WHITE, COLOR_BLACK, 0);
-+ }
-+ }
-+ }
-+ break;
-+
-+ case DOPT_BUTTON:
-+ break;
-+
-+ case DOPT_SUBMENU:
-+ if (opt == sCurrentOpt) {
-+ dynos_print_string(sDynosTextA, OFFSET_FROM_RIGHT_EDGE, y, COLOR_SELECT, COLOR_BLACK, 0);
-+ }
-+ break;
-+ }
-+}
-+
-+static void dynos_draw_menu() {
-+ if (sCurrentMenu == NULL) {
-+ return;
-+ }
-+
-+ // Colorful label
-+ const unsigned char *label2 = NULL;
-+ if (sCurrentOpt->parent) {
-+ label2 = get_label2(sCurrentOpt->parent);
-+ } else if (sCurrentMenu == sDynosMenu) {
-+ label2 = sDynosTextDynosMenu;
-+ } else {
-+ label2 = sDynosTextOptionsMenu;
-+ }
-+ gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin);
-+ gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255);
-+ print_hud_lut_string(HUD_LUT_GLOBAL, dynos_get_label2_x(SCREEN_WIDTH / 2, label2), 40, label2);
-+ gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end);
-+
-+ // Display options
-+ struct DynosOption **optionsToDraw = dynos_get_options_to_draw();
-+ for (int i = 0; i != 7; ++i) {
-+ dynos_draw_option(optionsToDraw[i], 156 - 20 * i);
-+ }
-+
-+ // Scroll bar
-+ int optCount = dynos_get_option_count();
-+ int optIndex = dynos_get_current_option_index();
-+ if (optCount > 7) {
-+ int h = (int) (134.f * sqrtf(1.f / (optCount - 6)));
-+ int y = 37 + (134 - h) * (1.f - MAX(0.f, MIN(1.f, (float)(optIndex - 3) / (float)(optCount - 6))));
-+ dynos_print_box(OFFSET_FROM_RIGHT_EDGE - 16, 36, 8, 136, COLOR_DARK_GRAY, 0);
-+ dynos_print_box(OFFSET_FROM_RIGHT_EDGE - 15, y, 6, h, COLOR_WHITE, 0);
-+ }
-+}
-+
-+//
-+// Processing
-+//
-+
-+#define SOUND_DYNOS_SAVED (SOUND_MENU_MARIO_CASTLE_WARP2 | (0xFF << 8))
-+#define SOUND_DYNOS_SELECT (SOUND_MENU_CHANGE_SELECT | (0xF8 << 8))
-+#define SOUND_DYNOS_OK (SOUND_MENU_CHANGE_SELECT | (0xF8 << 8))
-+#define SOUND_DYNOS_CANCEL (SOUND_MENU_CAMERA_BUZZ | (0xFC << 8))
-+
-+enum {
-+ INPUT_LEFT,
-+ INPUT_RIGHT,
-+ INPUT_A,
-+ INPUT_Z
-+};
-+
-+enum {
-+ RESULT_NONE,
-+ RESULT_OK,
-+ RESULT_CANCEL
-+};
-+
-+static int dynos_opt_process_input(struct DynosOption *opt, int input) {
-+ switch (opt->type) {
-+ case DOPT_TOGGLE:
-+ if (input == INPUT_LEFT) {
-+ *opt->toggle.ptog = false;
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_RIGHT) {
-+ *opt->toggle.ptog = true;
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_A) {
-+ *opt->toggle.ptog = !(*opt->toggle.ptog);
-+ return RESULT_OK;
-+ }
-+ break;
-+
-+ case DOPT_CHOICE:
-+ case DOPT_CHOICELEVEL:
-+ case DOPT_CHOICESTAR:
-+ if (input == INPUT_LEFT) {
-+ *opt->choice.pindex = (*opt->choice.pindex + opt->choice.count - 1) % (opt->choice.count);
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_RIGHT || input == INPUT_A) {
-+ *opt->choice.pindex = (*opt->choice.pindex + 1) % (opt->choice.count);
-+ return RESULT_OK;
-+ }
-+ break;
-+
-+ case DOPT_SCROLL:
-+ if (input == INPUT_LEFT) {
-+ *opt->scroll.pvalue = MAX(opt->scroll.min, *opt->scroll.pvalue - opt->scroll.step * (gPlayer1Controller->buttonDown & A_BUTTON ? 5 : 1));
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_RIGHT) {
-+ *opt->scroll.pvalue = MIN(opt->scroll.max, *opt->scroll.pvalue + opt->scroll.step * (gPlayer1Controller->buttonDown & A_BUTTON ? 5 : 1));
-+ return RESULT_OK;
-+ }
-+ break;
-+
-+ case DOPT_BIND:
-+ if (input == INPUT_LEFT) {
-+ opt->bind.index = MAX(0, opt->bind.index - 1);
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_RIGHT) {
-+ opt->bind.index = MIN(MAX_BINDS - 1, opt->bind.index + 1);
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_Z) {
-+ opt->bind.pbinds[opt->bind.index] = VK_INVALID;
-+ return RESULT_OK;
-+ }
-+ if (input == INPUT_A) {
-+ opt->bind.pbinds[opt->bind.index] = VK_INVALID;
-+ sBindingState = 1;
-+ controller_get_raw_key();
-+ return RESULT_OK;
-+ }
-+ break;
-+
-+ case DOPT_BUTTON:
-+ if (input == INPUT_A && opt->button.funcName != NULL) {
-+ DynosActionFunction action = dynos_get_action(opt->button.funcName);
-+ if (action != NULL && action(opt->name)) {
-+ return RESULT_OK;
-+ }
-+ return RESULT_CANCEL;
-+ }
-+ break;
-+
-+ case DOPT_SUBMENU:
-+ if (input == INPUT_A) {
-+ if (opt->submenu.child != NULL) {
-+ sCurrentOpt = opt->submenu.child;
-+ return RESULT_OK;
-+ }
-+ return RESULT_CANCEL;
-+ }
-+ break;
-+ }
-+ return RESULT_NONE;
-+}
-+
-+static void dynos_open(struct DynosOption *menu) {
-+ play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs);
-+ sCurrentMenu = menu;
-+ sCurrentOpt = menu;
-+}
-+
-+static void dynos_close() {
-+ if (sCurrentMenu != NULL) {
-+ play_sound(SOUND_DYNOS_SAVED, gDefaultSoundArgs);
-+ controller_reconfigure();
-+ configfile_save(configfile_name());
-+ dynos_save_config();
-+ sCurrentMenu = NULL;
-+ }
-+}
-+
-+static void dynos_process_inputs() {
-+ static int sStickTimer = 0;
-+ static bool sPrevStick = 0;
-+
-+ // Stick values
-+ float stickX = gPlayer1Controller->stickX;
-+ float stickY = gPlayer1Controller->stickY;
-+ if (absx(stickX) > 60 || absx(stickY) > 60) {
-+ if (sStickTimer == 0) {
-+ sStickTimer = (sPrevStick ? 2 : 9);
-+ } else {
-+ stickX = 0;
-+ stickY = 0;
-+ sStickTimer--;
-+ }
-+ sPrevStick = true;
-+ } else {
-+ sStickTimer = 0;
-+ sPrevStick = false;
-+ }
-+
-+ // Key binding
-+ if (sBindingState != 0) {
-+ unsigned int key = (sCurrentOpt->dynos ? (unsigned int) dynos_controller_get_key_pressed() : controller_get_raw_key());
-+ if (key != VK_INVALID) {
-+ play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs);
-+ sCurrentOpt->bind.pbinds[sCurrentOpt->bind.index] = key;
-+ sBindingState = false;
-+ }
-+ return;
-+ }
-+
-+ if (sCurrentMenu != NULL) {
-+
-+ // Up
-+ if (stickY > +60) {
-+ if (sCurrentOpt->prev != NULL) {
-+ sCurrentOpt = sCurrentOpt->prev;
-+ } else {
-+ while (sCurrentOpt->next) sCurrentOpt = sCurrentOpt->next;
-+ }
-+ play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs);
-+ return;
-+ }
-+
-+ // Down
-+ if (stickY < -60) {
-+ if (sCurrentOpt->next != NULL) {
-+ sCurrentOpt = sCurrentOpt->next;
-+ } else {
-+ while (sCurrentOpt->prev) sCurrentOpt = sCurrentOpt->prev;
-+ }
-+ play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs);
-+ return;
-+ }
-+
-+ // Left
-+ if (stickX < -60) {
-+ switch (dynos_opt_process_input(sCurrentOpt, INPUT_LEFT)) {
-+ case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break;
-+ case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break;
-+ case RESULT_NONE: break;
-+ }
-+ return;
-+ }
-+
-+ // Right
-+ if (stickX > +60) {
-+ switch (dynos_opt_process_input(sCurrentOpt, INPUT_RIGHT)) {
-+ case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break;
-+ case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break;
-+ case RESULT_NONE: break;
-+ }
-+ return;
-+ }
-+
-+ // A
-+ if (gPlayer1Controller->buttonPressed & A_BUTTON) {
-+ switch (dynos_opt_process_input(sCurrentOpt, INPUT_A)) {
-+ case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break;
-+ case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break;
-+ case RESULT_NONE: break;
-+ }
-+ return;
-+ }
-+
-+ // B
-+ if (gPlayer1Controller->buttonPressed & B_BUTTON) {
-+ if (sCurrentOpt->parent != NULL) {
-+ sCurrentOpt = sCurrentOpt->parent;
-+ play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs);
-+ } else {
-+ dynos_close();
-+ }
-+ return;
-+ }
-+
-+ // Z
-+ if (gPlayer1Controller->buttonPressed & Z_TRIG) {
-+ switch (dynos_opt_process_input(sCurrentOpt, INPUT_Z)) {
-+ case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break;
-+ case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break;
-+ case RESULT_NONE:
-+ if (sCurrentMenu == sDynosMenu) {
-+ dynos_close();
-+ } else {
-+ dynos_open(sDynosMenu);
-+ } break;
-+ }
-+ return;
-+ }
-+
-+ // R
-+ if (gPlayer1Controller->buttonPressed & R_TRIG) {
-+ if (sCurrentMenu == sOptionsMenu) {
-+ dynos_close();
-+ } else {
-+ dynos_open(sOptionsMenu);
-+ }
-+ return;
-+ }
-+
-+ // Start
-+ if (gPlayer1Controller->buttonPressed & START_BUTTON) {
-+ dynos_close();
-+ return;
-+ }
-+ } else if (gPlayer1Controller->buttonPressed & R_TRIG) {
-+ dynos_open(sOptionsMenu);
-+ } else if (gPlayer1Controller->buttonPressed & Z_TRIG) {
-+ dynos_open(sDynosMenu);
-+ }
-+}
-+
-+#define PROMPT_OFFSET (56.25f * GFX_DIMENSIONS_ASPECT_RATIO)
-+static void dynos_draw_prompt() {
-+ if (sCurrentMenu == sOptionsMenu) {
-+ dynos_print_string(sDynosTextOpenLeft, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1);
-+ dynos_print_string(sDynosTextCloseRight, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0);
-+ } else if (sCurrentMenu == sDynosMenu) {
-+ dynos_print_string(sDynosTextCloseLeft, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1);
-+ dynos_print_string(sDynosTextOpenRight, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0);
-+ } else {
-+ dynos_print_string(sDynosTextOpenLeft, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1);
-+ dynos_print_string(sDynosTextOpenRight, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0);
-+ }
-+ dynos_process_inputs();
-+#ifdef RENDER96_2_0
-+ set_language(languages[configLanguage]);
-+#endif
-+}
-+
-+//
-+// Init
-+//
-+
-+static void (*controller_read)(OSContPad *);
-+static void dynos_controller_read(OSContPad *pad) {
-+ controller_read(pad);
-+ dynos_loop(sDynosMenu, dynos_controller_update, (void *) pad);
-+}
-+
-+static void dynos_create_warp_to_level_options() {
-+ dynos_create_submenu("dynos_warp_submenu", "Warp to Level", "WARP TO LEUEL");
-+
-+ // Level select
-+ {
-+ struct DynosOption *opt = dynos_add_option("dynos_warp_level", NULL, "Level Select", "");
-+ opt->type = DOPT_CHOICELEVEL;
-+ opt->choice.count = level_get_count(true);
-+ opt->choice.pindex = calloc(1, sizeof(int));
-+ *opt->choice.pindex = 0;
-+ }
-+
-+ // Star select
-+ {
-+ struct DynosOption *opt = dynos_add_option("dynos_warp_act", NULL, "Star Select", "");
-+ opt->type = DOPT_CHOICESTAR;
-+ opt->choice.count = 6;
-+ opt->choice.pindex = calloc(1, sizeof(int));
-+ *opt->choice.pindex = 0;
-+ }
-+
-+ dynos_create_button("dynos_warp_to_level", "Warp", "dynos_warp_to_level");
-+ dynos_end_submenu();
-+}
-+
-+void dynos_init() {
-+
-+ // Convert options menu
-+ dynos_convert_options_menu();
-+
-+ // Create DynOS menu
-+ dynos_load_options();
-+
-+ // Warp to level
-+ dynos_create_warp_to_level_options();
-+
-+ // Restart level
-+ dynos_create_button("dynos_restart_level", "Restart Level", "dynos_restart_level");
-+
-+ // Return to main menu
-+ dynos_create_button("dynos_return_to_main_menu", "Return to Main Menu", "dynos_return_to_main_menu");
-+
-+ // Init config
-+ dynos_load_config();
-+
-+ // Init controller
-+ controller_read = controller_keyboard.read;
-+ controller_keyboard.read = dynos_controller_read;
-+
-+ // Init text
-+ sDynosTextDynosMenu = str64h("DYNOS MENU");
-+ sDynosTextA = str64h("([A]) >");
-+ sDynosTextOpenLeft = str64h("[Z] DynOS");
-+ sDynosTextCloseLeft = str64h("[Z] Return");
-+#ifndef RENDER96_2_0
-+ sDynosTextOptionsMenu = str64h("OPTIONS");
-+ sDynosTextDisabled = str64h("Disabled");
-+ sDynosTextEnabled = str64h("Enabled");
-+ sDynosTextNone = str64h("NONE");
-+ sDynosTextDotDotDot = str64h("...");
-+ sDynosTextOpenRight = str64h("[R] Options");
-+ sDynosTextCloseRight = str64h("[R] Return");
-+#endif
-+}
-+
-+//
-+// Hijack
-+//
-+
-+unsigned char optmenu_open = 0;
-+
-+void optmenu_toggle(void) {
-+ dynos_close();
-+}
-+
-+void optmenu_draw(void) {
-+ dynos_draw_menu();
-+}
-+
-+void optmenu_draw_prompt(void) {
-+ dynos_draw_prompt();
-+ optmenu_open = (sCurrentMenu != NULL);
-+}
-+
-+void optmenu_check_buttons(void) {
-+}
-+
-+//
-+// Return to Main Menu
-+//
-+
-+extern char gDialogBoxState;
-+extern short gMenuMode;
-+void dynos_unpause_game() {
-+ level_set_transition(0, 0);
-+ play_sound(SOUND_MENU_PAUSE_2, gDefaultSoundArgs);
-+ gDialogBoxState = 0;
-+ gMenuMode = -1;
-+}
-+
-+bool dynos_return_to_main_menu(UNUSED const char *optName) {
-+ optmenu_toggle();
-+ dynos_unpause_game();
-+ fade_into_special_warp(-2, 0);
-+ return true;
-+}
-+
-+//
-+// Warp to Level
-+//
-+
-+bool dynos_perform_warp(enum LevelNum levelNum, int actNum) {
-+ enum CourseNum courseNum = level_get_course(levelNum);
-+ if (courseNum == COURSE_NONE) {
-+ return false;
-+ }
-+
-+ // Free everything from the current level
-+ optmenu_toggle();
-+ dynos_unpause_game();
-+ set_sound_disabled(FALSE);
-+ play_shell_music();
-+ stop_shell_music();
-+ stop_cap_music();
-+ clear_objects();
-+ clear_area_graph_nodes();
-+ clear_areas();
-+ main_pool_pop_state();
-+
-+ // Reset Mario's state
-+ gMarioState->healCounter = 0;
-+ gMarioState->hurtCounter = 0;
-+ gMarioState->numCoins = 0;
-+ gMarioState->input = 0;
-+ gMarioState->controller->buttonPressed = 0;
-+ gHudDisplay.coins = 0;
-+
-+ // Load the new level
-+ gCurrLevelNum = levelNum;
-+ gCurrCourseNum = courseNum;
-+ gCurrActNum = (courseNum <= COURSE_STAGES_MAX ? actNum : 0);
-+ gDialogCourseActNum = gCurrActNum;
-+ gCurrAreaIndex = 1;
-+ level_script_execute((struct LevelCommand *) level_get_script(levelNum));
-+ sWarpDest.type = 2;
-+ sWarpDest.levelNum = gCurrLevelNum;
-+ sWarpDest.areaIdx = 1;
-+ sWarpDest.nodeId = 0x0A;
-+ sWarpDest.arg = 0;
-+ gSavedCourseNum = gCurrCourseNum;
-+ return true;
-+}
-+
-+bool dynos_warp_to_level(UNUSED const char *optName) {
-+ enum LevelNum levelNum = level_get_list(true, true)[dynos_get_value("dynos_warp_level")];
-+ return dynos_perform_warp(levelNum, dynos_get_value("dynos_warp_act") + 1);
-+}
-+
-+//
-+// Restart Level
-+//
-+
-+bool dynos_restart_level(UNUSED const char *optName) {
-+ enum LevelNum levelNum = (enum LevelNum) gCurrLevelNum;
-+ if (levelNum == LEVEL_BOWSER_1) levelNum = LEVEL_BITDW;
-+ if (levelNum == LEVEL_BOWSER_2) levelNum = LEVEL_BITFS;
-+ if (levelNum == LEVEL_BOWSER_3) levelNum = LEVEL_BITS;
-+ return dynos_perform_warp(levelNum, gCurrActNum);
-+}
-diff --git a/src/pc/dynamic_options.h b/src/pc/dynamic_options.h
-new file mode 100644
-index 00000000..d1b4cf84
---- /dev/null
-+++ b/src/pc/dynamic_options.h
-@@ -0,0 +1,33 @@
-+#ifndef DYNAMIC_OPTIONS_H
-+#define DYNAMIC_OPTIONS_H
-+
-+#ifdef __cplusplus
-+extern "C" {
-+#endif
-+
-+#include
-+int dynos_get_value(const char *name);
-+void dynos_set_value(const char *name, int value);
-+void dynos_add_action(const char *funcName, bool (*funcPtr)(const char *), bool overwrite);
-+
-+#ifdef __cplusplus
-+}
-+#endif
-+
-+// Warning: This is C++ code, use this macro inside a .cpp file
-+// The action signature is "bool (*) (const char *)"
-+// The input is the button name (not label)
-+// The output is the result of the action
-+#define DYNOS_DEFINE_ACTION(func) \
-+extern "C" { extern bool func(const char *); } \
-+class DynosAction_##func { \
-+public: \
-+ inline DynosAction_##func() { \
-+ dynos_add_action(#func, func, false); \
-+ } \
-+private: \
-+ static DynosAction_##func sDynosAction_##func; \
-+}; \
-+DynosAction_##func DynosAction_##func::sDynosAction_##func;
-+
-+#endif // DYNAMIC_OPTIONS_H
-\ No newline at end of file
-diff --git a/src/pc/dynamic_options_cpp.cpp b/src/pc/dynamic_options_cpp.cpp
-new file mode 100644
-index 00000000..f0d609db
---- /dev/null
-+++ b/src/pc/dynamic_options_cpp.cpp
-@@ -0,0 +1,17 @@
-+#include "dynamic_options.h"
-+
-+//
-+// DynOS Init
-+//
-+
-+extern "C" { extern void dynos_init(void); }
-+class DynosInitialization { public: DynosInitialization() { dynos_init(); } };
-+static DynosInitialization sDynosInitialization;
-+
-+//
-+// DynOS Actions
-+//
-+
-+DYNOS_DEFINE_ACTION(dynos_return_to_main_menu);
-+DYNOS_DEFINE_ACTION(dynos_warp_to_level);
-+DYNOS_DEFINE_ACTION(dynos_restart_level);
-diff --git a/src/pc/levels.c b/src/pc/levels.c
-new file mode 100644
-index 00000000..cdb772c5
---- /dev/null
-+++ b/src/pc/levels.c
-@@ -0,0 +1,240 @@
-+#include
-+#include
-+#include
-+#include "utils.h"
-+#include "levels.h"
-+#include "game/segment2.h"
-+#ifdef RENDER96_2_0
-+#include "text/text-loader.h"
-+#endif
-+
-+#define STUB_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, _8)
-+#define DEFINE_LEVEL(_0, _1, _2, _name, _4, _5, _6, _7, _8, _9, _10) extern const LevelScript level_##_name##_entry[];
-+#include "levels/level_defines.h"
-+#undef DEFINE_LEVEL
-+#undef STUB_LEVEL
-+
-+//
-+// Level, Course, Script
-+//
-+
-+struct LevelCourseScript {
-+ enum LevelNum level;
-+ enum CourseNum course;
-+ const LevelScript *script;
-+};
-+
-+#define STUB_LEVEL(_0, _1, _2, _3, _4, _5, _6, _7, _8)
-+#define DEFINE_LEVEL(_0, _level, _course, _name, _4, _5, _6, _7, _8, _9, _10) { _level, _course, level_##_name##_entry },
-+static const struct LevelCourseScript sLevelCourseScript[] = {
-+#include "levels/level_defines.h"
-+};
-+static const int sLevelCourseScriptCount = sizeof(sLevelCourseScript) / sizeof(sLevelCourseScript[0]);
-+#undef DEFINE_LEVEL
-+#undef STUB_LEVEL
-+
-+static const u8 sEmpty[] = { 255 };
-+static const u8 sCastle[] = { 12, 36, 54, 55, 47, 40, 255 };
-+static const u8 sBowser1[] = { 11, 50, 58, 54, 40, 53, 158, 1, 255 };
-+static const u8 sBowser2[] = { 11, 50, 58, 54, 40, 53, 158, 2, 255 };
-+static const u8 sBowser3[] = { 11, 50, 58, 54, 40, 53, 158, 3, 255 };
-+static const u8 s100CoinsStar[] = { 1, 0, 0, 158, 12, 50, 44, 49, 54, 158, 28, 55, 36, 53, 255 };
-+
-+//
-+// Accessors
-+//
-+
-+static enum CourseNum get_course(enum LevelNum levelNum) {
-+ for (int i = 0; i != sLevelCourseScriptCount; ++i) {
-+ if (sLevelCourseScript[i].level == levelNum) {
-+ return sLevelCourseScript[i].course;
-+ }
-+ }
-+ return COURSE_NONE;
-+}
-+
-+static const LevelScript *get_script(enum LevelNum levelNum) {
-+ for (int i = 0; i != sLevelCourseScriptCount; ++i) {
-+ if (sLevelCourseScript[i].level == levelNum) {
-+ return sLevelCourseScript[i].script;
-+ }
-+ }
-+ return NULL;
-+}
-+
-+static void set_level_name(u8 *buffer, enum LevelNum levelNum) {
-+ if (levelNum == LEVEL_BOWSER_1) { memcpy(buffer, sBowser1, str64l(sBowser1)); return; }
-+ if (levelNum == LEVEL_BOWSER_2) { memcpy(buffer, sBowser2, str64l(sBowser2)); return; }
-+ if (levelNum == LEVEL_BOWSER_3) { memcpy(buffer, sBowser3, str64l(sBowser3)); return; }
-+
-+ enum CourseNum courseNum = get_course(levelNum);
-+ if (courseNum < COURSE_BOB) { memcpy(buffer, sCastle, str64l(sCastle)); return; }
-+ if (courseNum >= COURSE_CAKE_END) { memcpy(buffer, sCastle, str64l(sCastle)); return; }
-+
-+ const u8 *courseName = ((const u8 **) seg2_course_name_table)[courseNum - COURSE_BOB] + 3;
-+ memcpy(buffer, courseName, str64l(courseName));
-+}
-+
-+static void set_act_name(u8 *buffer, enum LevelNum levelNum, int starNum) {
-+ enum CourseNum courseNum = get_course(levelNum);
-+ if (courseNum > COURSE_STAGES_MAX) { memcpy(buffer, sEmpty, str64l(sEmpty)); return; }
-+ if (starNum == 7) { memcpy(buffer, s100CoinsStar, str64l(s100CoinsStar)); return; }
-+
-+ const u8 *actName = ((const u8 **) seg2_act_name_table)[(courseNum - COURSE_BOB) * 6 + starNum - 1];
-+ memcpy(buffer, actName, str64l(actName));
-+}
-+
-+static void prefix_number(u8 *buffer, int num) {
-+ memmove(buffer + 5, buffer, str64l(buffer));
-+ buffer[0] = ((num / 10) == 0 ? 158 : (num / 10));
-+ buffer[1] = (num % 10);
-+ buffer[2] = 158;
-+ buffer[3] = 159;
-+ buffer[4] = 158;
-+}
-+
-+//
-+// Data
-+//
-+
-+static int sLevelCount[2] = { 0, 0 };
-+static enum LevelNum *sLevelList[2][2] = { { NULL, NULL }, { NULL, NULL } };
-+static enum CourseNum *sLevelCourses = NULL;
-+static const LevelScript **sLevelScripts = NULL;
-+
-+// Runs only once
-+static void level_init_data() {
-+ static bool inited = false;
-+ if (!inited) {
-+
-+ // Level count
-+ sLevelCount[0] = sLevelCourseScriptCount;
-+
-+ // Level count (no Castle)
-+ sLevelCount[1] = 0;
-+ for (int i = 0; i != sLevelCount[0]; ++i) {
-+ if (sLevelCourseScript[i].course >= COURSE_BOB &&
-+ sLevelCourseScript[i].course < COURSE_CAKE_END) {
-+ sLevelCount[1]++;
-+ }
-+ }
-+
-+ // Lists allocation
-+ sLevelList[0][0] = calloc(sLevelCount[0], sizeof(enum LevelNum));
-+ sLevelList[0][1] = calloc(sLevelCount[0], sizeof(enum LevelNum));
-+ sLevelList[1][0] = calloc(sLevelCount[1], sizeof(enum LevelNum));
-+ sLevelList[1][1] = calloc(sLevelCount[1], sizeof(enum LevelNum));
-+ sLevelCourses = calloc(LEVEL_COUNT, sizeof(enum CourseNum));
-+ sLevelScripts = calloc(LEVEL_COUNT, sizeof(const LevelScript *));
-+
-+ // Level list
-+ for (int i = 0; i != sLevelCount[0]; ++i) {
-+ sLevelList[0][0][i] = sLevelCourseScript[i].level;
-+ }
-+
-+ // Level list ordered by course id
-+ for (int i = 0, k = 0; i < COURSE_END; ++i) {
-+ for (int j = 0; j < sLevelCount[0]; ++j) {
-+ if (sLevelCourseScript[j].course == (enum CourseNum) i) {
-+ sLevelList[0][1][k++] = sLevelCourseScript[j].level;
-+ }
-+ }
-+ }
-+
-+ // Level list (no Castle)
-+ for (int i = 0, k = 0; i != sLevelCount[0]; ++i) {
-+ if (sLevelCourseScript[i].course >= COURSE_BOB &&
-+ sLevelCourseScript[i].course < COURSE_CAKE_END) {
-+ sLevelList[1][0][k++] = sLevelCourseScript[i].level;
-+ }
-+ }
-+
-+ // Level list ordered by course id (no Castle)
-+ for (int i = COURSE_BOB, k = 0; i < COURSE_CAKE_END; ++i) {
-+ for (int j = 0; j < sLevelCount[0]; ++j) {
-+ if (sLevelCourseScript[j].course == (enum CourseNum) i) {
-+ sLevelList[1][1][k++] = sLevelCourseScript[j].level;
-+ }
-+ }
-+ }
-+
-+ // Level courses
-+ for (int i = 0; i != LEVEL_COUNT; ++i) {
-+ sLevelCourses[i] = get_course((enum LevelNum) i);
-+ }
-+
-+ // Level scripts
-+ for (int i = 0; i != LEVEL_COUNT; ++i) {
-+ sLevelScripts[i] = get_script((enum LevelNum) i);
-+ }
-+
-+ // Done
-+ inited = true;
-+ }
-+}
-+
-+//
-+// Getters
-+//
-+
-+int level_get_count(bool noCastle) {
-+ level_init_data();
-+ return sLevelCount[noCastle];
-+}
-+
-+const enum LevelNum *level_get_list(bool noCastle, bool ordered) {
-+ level_init_data();
-+ return sLevelList[noCastle][ordered];
-+}
-+
-+enum CourseNum level_get_course(enum LevelNum levelNum) {
-+ level_init_data();
-+ return sLevelCourses[levelNum];
-+}
-+
-+const LevelScript *level_get_script(enum LevelNum levelNum) {
-+ level_init_data();
-+ return sLevelScripts[levelNum];
-+}
-+
-+const u8 *level_get_name(enum LevelNum levelNum, bool decaps, bool addCourseNumber) {
-+ level_init_data();
-+ static u8 buffer[256];
-+ memset(buffer, 0xFF, 256);
-+
-+ // Level name
-+ set_level_name(buffer, levelNum);
-+
-+ // Decaps
-+ if (decaps) str64d(buffer);
-+
-+ // Course number
-+ if (addCourseNumber) {
-+ enum CourseNum courseNum = get_course(levelNum);
-+ if (courseNum >= COURSE_BOB && courseNum <= COURSE_STAGES_MAX)
-+ prefix_number(buffer, courseNum);
-+ }
-+
-+ return buffer;
-+}
-+
-+const u8 *level_get_star_name(enum LevelNum levelNum, int starNum, bool decaps, bool addStarNumber) {
-+ level_init_data();
-+ static u8 buffer[256];
-+ memset(buffer, 0xFF, 256);
-+
-+ // Star name
-+ set_act_name(buffer, levelNum, starNum);
-+
-+ // Decaps
-+ if (decaps) str64d(buffer);
-+
-+ // Star number
-+ if (addStarNumber) {
-+ enum CourseNum courseNum = get_course(levelNum);
-+ if (courseNum >= COURSE_BOB && courseNum <= COURSE_STAGES_MAX)
-+ prefix_number(buffer, starNum);
-+ }
-+
-+ return buffer;
-+}
-diff --git a/src/pc/levels.h b/src/pc/levels.h
-new file mode 100644
-index 00000000..82415fd8
---- /dev/null
-+++ b/src/pc/levels.h
-@@ -0,0 +1,21 @@
-+#ifndef LEVELS_H
-+#define LEVELS_H
-+
-+/*
-+This file helps to provide useful info about the game's levels,
-+such as level count, level list, courses, scripts and names.
-+*/
-+
-+#include
-+#include "types.h"
-+#include "level_table.h"
-+#include "course_table.h"
-+
-+int level_get_count(bool noCastle);
-+const enum LevelNum *level_get_list(bool noCastle, bool ordered);
-+enum CourseNum level_get_course(enum LevelNum levelNum);
-+const LevelScript *level_get_script(enum LevelNum levelNum);
-+const u8 *level_get_name(enum LevelNum levelNum, bool decaps, bool addCourseNumber);
-+const u8 *level_get_star_name(enum LevelNum levelNum, int starNum, bool decaps, bool addStarNumber);
-+
-+#endif // LEVELS_H
-\ No newline at end of file
-diff --git a/src/pc/utils.c b/src/pc/utils.c
-new file mode 100644
-index 00000000..edbdd1d6
---- /dev/null
-+++ b/src/pc/utils.c
-@@ -0,0 +1,214 @@
-+#include "utils.h"
-+#include "game/object_list_processor.h"
-+#include "game/object_helpers.h"
-+#include
-+#include
-+#include
-+#include
-+
-+//
-+// C dynamic array, vector-like struct
-+//
-+
-+static void __dynamic_array_realloc(struct __DynamicArray *da, int newcapacity) {
-+ if (newcapacity > da->capacity) {
-+ void *newbuffer = calloc(newcapacity, da->itemsize);
-+ if (da->buffer) {
-+ memcpy(newbuffer, da->buffer, da->count * da->itemsize);
-+ free(da->buffer);
-+ }
-+ da->buffer = newbuffer;
-+ da->capacity = newcapacity;
-+ }
-+}
-+
-+void __dynamic_array_init(struct __DynamicArray *da, int itemsize) {
-+ da->buffer = NULL;
-+ da->count = 0;
-+ da->capacity = 0;
-+ da->itemsize = itemsize;
-+}
-+
-+void __dynamic_array_add(struct __DynamicArray *da, void *item) {
-+ __dynamic_array_realloc(da, da->count + 1);
-+ memcpy((void *) ((size_t) da->buffer + (size_t) da->count * (size_t) da->itemsize), item, (size_t) da->itemsize);
-+ da->count++;
-+}
-+
-+void __dynamic_array_rem(struct __DynamicArray *da, int index) {
-+ if (index == -1) {
-+ da->count = 0;
-+ } else if (index >= da->count - 1) {
-+ da->count--;
-+ } else {
-+ memmove((void *) ((size_t) da->buffer + (size_t) (index + 0) * (size_t) da->itemsize),
-+ (void *) ((size_t) da->buffer + (size_t) (index + 1) * (size_t) da->itemsize),
-+ (size_t) (da->count - index - 1) * (size_t) da->itemsize);
-+ da->count--;
-+ }
-+}
-+
-+void __dynamic_array_clr(struct __DynamicArray *da) {
-+ if (da->buffer) {
-+ free(da->buffer);
-+ }
-+ da->buffer = NULL;
-+ da->count = 0;
-+ da->capacity = 0;
-+}
-+
-+void *__dynamic_array_get(struct __DynamicArray *da, int index) {
-+ return (void *) ((size_t) da->buffer + (size_t) index * (size_t) da->itemsize);
-+}
-+
-+void __dynamic_array_set(struct __DynamicArray *da, int index, void *item) {
-+ memcpy((void *) ((size_t) da->buffer + (size_t) index * (size_t) da->itemsize), item, (size_t) da->itemsize);
-+}
-+
-+int __dynamic_array_find(struct __DynamicArray *da, void *item) {
-+ void *curr = da->buffer;
-+ void *end = (void *) ((size_t) da->buffer + (size_t) da->count * (size_t) da->itemsize);
-+ for (int i = 0; curr != end; ++i) {
-+ if (memcmp(curr, item, (size_t) da->itemsize) == 0) {
-+ return i;
-+ }
-+ curr = (void *) ((size_t) curr + (size_t) da->itemsize);
-+ }
-+ return -1;
-+}
-+
-+int __dynamic_array_find_eq(struct __DynamicArray *da, void *item, bool (*eqfunc)(void *, void *)) {
-+ void *curr = da->buffer;
-+ void *end = (void *) ((size_t) da->buffer + (size_t) da->count * (size_t) da->itemsize);
-+ for (int i = 0; curr != end; ++i) {
-+ if (eqfunc(curr, item)) {
-+ return i;
-+ }
-+ curr = (void *) ((size_t) curr + (size_t) da->itemsize);
-+ }
-+ return -1;
-+}
-+
-+//
-+// C String to SM64 String conversion
-+//
-+
-+static const struct { const char *str; unsigned char c64; int w; } sSm64CharMap[] = {
-+ { "0", 0x00, 7 }, { "1", 0x01, 7 }, { "2", 0x02, 7 }, { "3", 0x03, 7 }, { "4", 0x04, 7 }, { "5", 0x05, 7 },
-+ { "6", 0x06, 7 }, { "7", 0x07, 7 }, { "8", 0x08, 7 }, { "9", 0x09, 7 }, { "A", 0x0A, 6 }, { "B", 0x0B, 6 },
-+ { "C", 0x0C, 6 }, { "D", 0x0D, 6 }, { "E", 0x0E, 6 }, { "F", 0x0F, 6 }, { "G", 0x10, 6 }, { "H", 0x11, 6 },
-+ { "I", 0x12, 5 }, { "J", 0x13, 6 }, { "K", 0x14, 6 }, { "L", 0x15, 5 }, { "M", 0x16, 8 }, { "N", 0x17, 8 },
-+ { "O", 0x18, 6 }, { "P", 0x19, 6 }, { "Q", 0x1A, 6 }, { "R", 0x1B, 6 }, { "S", 0x1C, 6 }, { "T", 0x1D, 5 },
-+ { "U", 0x1E, 6 }, { "V", 0x1F, 6 }, { "W", 0x20, 8 }, { "X", 0x21, 7 }, { "Y", 0x22, 6 }, { "Z", 0x23, 6 },
-+ { "a", 0x24, 6 }, { "b", 0x25, 5 }, { "c", 0x26, 5 }, { "d", 0x27, 6 }, { "e", 0x28, 5 }, { "f", 0x29, 5 },
-+ { "g", 0x2A, 6 }, { "h", 0x2B, 5 }, { "i", 0x2C, 4 }, { "j", 0x2D, 5 }, { "k", 0x2E, 5 }, { "l", 0x2F, 3 },
-+ { "m", 0x30, 7 }, { "n", 0x31, 5 }, { "o", 0x32, 5 }, { "p", 0x33, 5 }, { "q", 0x34, 6 }, { "r", 0x35, 5 },
-+ { "s", 0x36, 5 }, { "t", 0x37, 5 }, { "u", 0x38, 5 }, { "v", 0x39, 5 }, { "w", 0x3A, 7 }, { "x", 0x3B, 7 },
-+ { "y", 0x3C, 5 }, { "z", 0x3D, 5 }, { "\'", 0x3E, 4 }, { ".", 0x3F, 4 }, { "^", 0x50, 8 }, { "|", 0x51, 8 },
-+ { "<", 0x52, 8 }, { ">", 0x53, 8 }, { "[A]", 0x54, 7 }, { "[B]", 0x55, 7 }, { "[C]", 0x56, 6 }, { "[Z]", 0x57, 7 },
-+ { "[R]", 0x58, 7 }, { ",", 0x6F, 4 }, { " ", 0x9E, 5 }, { "-", 0x9F, 6 }, { "/", 0xD0, 10 }, { "[%]", 0xE0, 7 },
-+ { "(", 0xE1, 5 }, { ")(", 0xE2, 10 }, { ")", 0xE3, 5 }, { "+", 0xE4, 9 }, { "&", 0xE5, 8 }, { ":", 0xE6, 4 },
-+ { "!", 0xF2, 5 }, { "%", 0xF3, 7 }, { "?", 0xF4, 7 }, { "~", 0xF7, 8 }, { "$", 0xF9, 8 }, { "@", 0xFA, 10 },
-+ { "*", 0xFB, 6 }, { "¤", 0xFD, 10 }, { "\n", 0xFE, 0 }, { "\0", 0xFF, 0 },
-+};
-+static const int sSm64CharCount = sizeof(sSm64CharMap) / sizeof(sSm64CharMap[0]);
-+
-+static const char *__sm64_add_char(unsigned char *str64, const char *str, int *i) {
-+ for (int k = 0; k != sSm64CharCount; ++k) {
-+ if (strstr(str, sSm64CharMap[k].str) == str) {
-+ str64[(*i)++] = sSm64CharMap[k].c64;
-+ return str + strlen(sSm64CharMap[k].str);
-+ }
-+ }
-+ return str + 1;
-+}
-+
-+#define STRING_MAX_LENGTH 2048
-+unsigned char *__sm64_string(bool heapAlloc, const char *fmt, ...) {
-+
-+ // Format
-+ char buffer[STRING_MAX_LENGTH];
-+ va_list arg;
-+ va_start(arg, fmt);
-+ vsnprintf(buffer, STRING_MAX_LENGTH, fmt, arg);
-+ va_end(arg);
-+
-+ // Allocation
-+ static unsigned char sStringBuffer[8][STRING_MAX_LENGTH];
-+ static unsigned int sStringBufferIndex = 0;
-+ unsigned char *str64;
-+ if (heapAlloc) {
-+ str64 = calloc(STRING_MAX_LENGTH, sizeof(unsigned char));
-+ } else {
-+ str64 = sStringBuffer[sStringBufferIndex];
-+ sStringBufferIndex = (sStringBufferIndex + 1) % 8;
-+ }
-+
-+ // Conversion
-+ memset(str64, 0xFF, STRING_MAX_LENGTH);
-+ const char *str = &buffer[0];
-+ for (int i = 0; *str != 0 && i < STRING_MAX_LENGTH - 1;) {
-+ str = __sm64_add_char(str64, str, &i);
-+ }
-+ return str64;
-+}
-+
-+unsigned char *__sm64_string_decapitalize(unsigned char *str64) {
-+ bool wasSpace = true;
-+ for (unsigned char *p = str64; *p != 255; p++) {
-+ if (*p >= 10 && *p <= 35) {
-+ if (wasSpace) wasSpace = false;
-+ else *p += 26;
-+ } else if (*p >= 63) {
-+ wasSpace = true;
-+ }
-+ }
-+ return str64;
-+}
-+
-+int __sm64_strlen(const unsigned char *str64) {
-+ int len = 0;
-+ for (; str64 && *str64 != 255; str64++, len++);
-+ return len;
-+}
-+
-+int __sm64_cwidth(unsigned char c64) {
-+ for (int k = 0; k != sSm64CharCount; ++k) {
-+ if (sSm64CharMap[k].c64 == c64) {
-+ return sSm64CharMap[k].w;
-+ }
-+ }
-+ return 0;
-+}
-+
-+//
-+// Dynamic Object Graph Node allocation
-+//
-+
-+#define ALLOC_SIZE 0x10000
-+static DA sLoadedGraphNodes = da_type(void *);
-+
-+void *get_graph_node_from_geo(const void *geoLayout) {
-+ int i = da_find(sLoadedGraphNodes, geoLayout);
-+ if (i != -1) {
-+ return da_get(sLoadedGraphNodes, i + 1, void *);
-+ }
-+
-+ struct AllocOnlyPool *pool = calloc(1, ALLOC_SIZE);
-+ pool->totalSpace = ALLOC_SIZE;
-+ pool->usedSpace = 0;
-+ pool->startPtr = (u8 *) pool + sizeof(struct AllocOnlyPool);
-+ pool->freePtr = (u8 *) pool + sizeof(struct AllocOnlyPool);
-+ void *graphNode = process_geo_layout(pool, (void *) geoLayout);
-+ if (graphNode) {
-+ da_add(sLoadedGraphNodes, geoLayout);
-+ da_add(sLoadedGraphNodes, graphNode);
-+ }
-+ return graphNode;
-+}
-+
-+void *obj_spawn_with_geo(void *parent, const void *geoLayout, const void *behavior) {
-+ struct Object *obj = spawn_object(parent, 0, behavior);
-+ obj->header.gfx.sharedChild = (struct GraphNode *) get_graph_node_from_geo(geoLayout);
-+ return obj;
-+}
-diff --git a/src/pc/utils.h b/src/pc/utils.h
-new file mode 100644
-index 00000000..0d8ecfab
---- /dev/null
-+++ b/src/pc/utils.h
-@@ -0,0 +1,52 @@
-+#ifndef PC_UTILS_H
-+#define PC_UTILS_H
-+
-+#include
-+
-+/* C dynamic array, vector-like struct */
-+struct __DynamicArray {
-+ void *buffer;
-+ int count;
-+ int capacity;
-+ int itemsize;
-+};
-+void __dynamic_array_init(struct __DynamicArray *da, int itemsize);
-+void __dynamic_array_add(struct __DynamicArray *da, void *item);
-+void __dynamic_array_rem(struct __DynamicArray *da, int index);
-+void __dynamic_array_clr(struct __DynamicArray *da);
-+void *__dynamic_array_get(struct __DynamicArray *da, int index);
-+void __dynamic_array_set(struct __DynamicArray *da, int index, void *item);
-+int __dynamic_array_find(struct __DynamicArray *da, void *item);
-+int __dynamic_array_find_eq(struct __DynamicArray *da, void *item, bool (*eqfunc)(void *, void *));
-+
-+#define DA struct __DynamicArray
-+#define da_type(type) { NULL, 0, 0, sizeof(type) }
-+#define da_init(da, type) __dynamic_array_init(&(da), sizeof(type))
-+#define da_add(da, item) __dynamic_array_add(&(da), (void *) &(item))
-+#define da_rem(da, index) __dynamic_array_rem(&(da), (index))
-+#define da_rem_all(da) __dynamic_array_rem(&(da), -1)
-+#define da_clr(da) __dynamic_array_clr(&(da))
-+#define da_get(da, index, type) (*((type *) __dynamic_array_get(&(da), (index))))
-+#define da_getp(da, index, type) ((type *) __dynamic_array_get(&(da), (index)))
-+#define da_set(da, index, item) __dynamic_array_set(&(da), (index), (void *) &(item))
-+#define da_item(ptr, type) (*((type *) ptr))
-+#define da_find(da, item) __dynamic_array_find(&(da), (void *) &(item))
-+#define da_find_eq(da, item, eqfunc) __dynamic_array_find_eq(&(da), (void *) &(item), eqfunc)
-+
-+/* String conversion */
-+unsigned char *__sm64_string(bool heapAlloc, const char *fmt, ...);
-+unsigned char *__sm64_string_decapitalize(unsigned char *str64);
-+int __sm64_strlen(const unsigned char *str64);
-+int __sm64_cwidth(unsigned char c64);
-+
-+#define str64s(...) __sm64_string(false, __VA_ARGS__) /* String allocated on stack (circular buffer of static strings) */
-+#define str64h(...) __sm64_string(true, __VA_ARGS__) /* String allocated on heap */
-+#define str64l(str64) __sm64_strlen(str64) /* String length */
-+#define str64d(str64) __sm64_string_decapitalize(str64) /* String decapitalization (does not alloc a new string, return the modified string) */
-+#define str64w(c64) __sm64_cwidth(c64) /* SM64 Char width */
-+
-+/* Dynamically allocated graph nodes */
-+void *get_graph_node_from_geo(const void *geoLayout);
-+void *obj_spawn_with_geo(void *parent, const void *geoLayout, const void *behavior);
-+
-+#endif
-\ No newline at end of file
-diff --git a/texts/AM_us.json b/texts/AM_us.json
-index 871a4780..3ba8f8af 100644
---- a/texts/AM_us.json
-+++ b/texts/AM_us.json
-@@ -3346,7 +3346,104 @@
- "TEXT_OPT_CHEAT8": "Huge Mario",
- "TEXT_OPT_CHEAT9": "Tiny Mario",
- "TEXT_OPT_GAME": "GAME",
-- "TEXT_OPT_LANGUAGE": "Current Language"
-+ "TEXT_OPT_LANGUAGE": "Current Language",
-+ "TEXT_OPT_COIN": "COIN CHEATS (HOLD B)",
-+ "TEXT_OPT_HOVER": "HOVER MODE",
-+ "TEXT_OPT_MOON": "MOON GRAVITY",
-+ "TEXT_OPT_RUN": "RUN SPEED",
-+ "TEXT_OPT_NDB": "NO DEATH BARRIER",
-+ "TEXT_OPT_JUMP": "ALL JUMPS HIGHER",
-+ "TEXT_OPT_SPDDPS": "SPEED DISPLAY",
-+ "TEXT_OPT_TPF": "T POSE FLOAT",
-+ "TEXT_OPT_JB": "SONG LIST",
-+ "TEXT_OPT_JBC": "PLAY SONG",
-+ "TEXT_OPT_QUIKEND": "QUICK ENDING",
-+ "TEXT_OPT_HURT": "HURT MARIO L + A",
-+ "TEXT_OPT_CANN": "CANNON ANYWHERE L + C Up",
-+ "TEXT_OPT_AWK": "AUTOWALLKICK",
-+ "TEXT_OPT_SHELL": "GET SHELL L + R",
-+ "TEXT_OPT_BOB": "GET BOBOMB L + R",
-+ "TEXT_OPT_SPAMBA": "SPAMBA L + Z",
-+ "TEXT_OPT_SWIM": "QUICK SWIM",
-+ "TEXT_OPT_WING_CAP": "GET WING CAP",
-+ "TEXT_OPT_METAL_CAP": "GET METAL CAP",
-+ "TEXT_OPT_VANISH_CAP": "GET VANISH CAP",
-+ "TEXT_OPT_REMOVE_CAP": "REMOVE CAP",
-+ "TEXT_OPT_NORMAL_CAP": "RESET CAP",
-+ "TEXT_OPT_BLJ": "BLJ ANYWHERE",
-+ "TEXT_OPT_PAC": "PLAY AS",
-+ "TEXT_OPT_TRIPLE": "ALL JUMPS TRIPLE",
-+ "TEXT_OPT_FLY": "FLYER",
-+ "TEXT_OPT_NOB": "NO BOUNDS",
-+ "TEXT_OPT_FLJ": "FORWARD LONG JUMP",
-+ "TEXT_OPT_TS": "TIME STOP",
-+ "TEXT_OPT_COIN1": "OFF",
-+ "TEXT_OPT_COIN2": "COIN",
-+ "TEXT_OPT_COIN3": "BLUE COIN",
-+ "TEXT_OPT_COIN4": "RED COIN",
-+ "TEXT_OPT_SS1": "NORMAL",
-+ "TEXT_OPT_SS2": "SLOW",
-+ "TEXT_OPT_SS3": "SLOWER",
-+ "TEXT_OPT_SS4": "FAST",
-+ "TEXT_OPT_SS5": "FASTER",
-+ "TEXT_OPT_PA1": "DISABLED",
-+ "TEXT_OPT_PA2": "BLACK BOBOMB",
-+ "TEXT_OPT_PA3": "PINK BOBOMB",
-+ "TEXT_OPT_PA4": "GOOMBA",
-+ "TEXT_OPT_PA5": "KOOPA SHELL",
-+ "TEXT_OPT_PA6": "CHUCKYA",
-+ "TEXT_OPT_PA7": "FLYGUY",
-+ "TEXT_OPT_PA8": "PER LEVEL",
-+ "TEXT_OPT_SEQ1": "INTRO",
-+ "TEXT_OPT_SEQ2": "GRASS",
-+ "TEXT_OPT_SEQ3": "CASTLE",
-+ "TEXT_OPT_SEQ4": "WATER",
-+ "TEXT_OPT_SEQ5": "HOT",
-+ "TEXT_OPT_SEQ6": "BOWSER",
-+ "TEXT_OPT_SEQ7": "SNOW",
-+ "TEXT_OPT_SEQ8": "SLIDE",
-+ "TEXT_OPT_SEQ9": "SPOOKY",
-+ "TEXT_OPT_SEQ10": "UNDERGROUND",
-+ "TEXT_OPT_SEQ11": "KOOPA ROAD",
-+ "TEXT_OPT_SEQ12": "FINAL BOWSER",
-+ "TEXT_OPT_SEQ13": "TITLE",
-+ "TEXT_OPT_SEQ14": "FILE SELECT",
-+ "TEXT_OPT_SEQ15": "POWERUP",
-+ "TEXT_OPT_SEQ16": "METAL CAP",
-+ "TEXT_OPT_SEQ17": "BOSS",
-+ "TEXT_OPT_SEQ18": "MERRYGOROUND",
-+ "TEXT_OPT_SEQ19": "CREDITS",
-+ "TEXT_OPT_HURTCHT1": "DISABLED",
-+ "TEXT_OPT_HURTCHT2": "BURN",
-+ "TEXT_OPT_HURTCHT3": "SHOCK",
-+ "TEXT_OPT_HURTCHT4": "ONE HP",
-+ "TEXT_OPT_SPAMCHT1": "DISABLED",
-+ "TEXT_OPT_SPAMCHT2": "AMP",
-+ "TEXT_OPT_SPAMCHT3": "BLUE COIN SWITCH",
-+ "TEXT_OPT_SPAMCHT4": "BOWLING BALL",
-+ "TEXT_OPT_SPAMCHT5": "BREAKABLE BOX",
-+ "TEXT_OPT_SPAMCHT6": "BREAKABLE BOX SMALL",
-+ "TEXT_OPT_SPAMCHT7": "JUMPING BOX",
-+ "TEXT_OPT_SPAMCHT8": "CHECKERBOARD PLATFORM",
-+ "TEXT_OPT_SPAMCHT9": "CHUCKYA",
-+ "TEXT_OPT_SPAMCHT10": "FLYGUY",
-+ "TEXT_OPT_SPAMCHT11": "GOOMBAS",
-+ "TEXT_OPT_SPAMCHT12": "HEART",
-+ "TEXT_OPT_SPAMCHT13": "METAL BOX",
-+ "TEXT_OPT_SPAMCHT14": "PURPLE SWITCH",
-+ "TEXT_OPT_BLJCHT1": "DISABLED",
-+ "TEXT_OPT_BLJCHT2": "ENABLED",
-+ "TEXT_OPT_BLJCHT3": "ENABLED - BOOST: 1",
-+ "TEXT_OPT_BLJCHT4": "ENABLED - BOOST: 2",
-+ "TEXT_OPT_BLJCHT5": "ENABLED - BOOST: 3",
-+ "TEXT_OPT_BLJCHT6": "ENABLED - BOOST: 4",
-+ "TEXT_OPT_BLJCHT7": "ENABLED - BOOST: 5",
-+ "TEXT_OPT_BLJCHT8": "RAPID FIRE",
-+ "TEXT_OPT_BLJCHT9": "RAPID FIRE - BOOST: 1",
-+ "TEXT_OPT_BLJCHT10": "RAPID FIRE - BOOST: 2",
-+ "TEXT_OPT_BLJCHT11": "RAPID FIRE - BOOST: 3",
-+ "TEXT_OPT_BLJCHT12": "RAPID FIRE - BOOST: 4",
-+ "TEXT_OPT_BLJCHT13": "RAPID FIRE - BOOST: 5"
- },
- "strings": {
- "TEXT_ZERO": "0",
-diff --git a/texts/ES_es.json b/texts/ES_es.json
-index eff2f962..73bf7895 100644
---- a/texts/ES_es.json
-+++ b/texts/ES_es.json
-@@ -3532,7 +3532,104 @@
- "TEXT_OPT_CHEAT8": "Mario gigante",
- "TEXT_OPT_CHEAT9": "Mario enano",
- "TEXT_OPT_GAME": "JUEGO",
-- "TEXT_OPT_LANGUAGE": "Idioma actual"
-+ "TEXT_OPT_LANGUAGE": "Idioma actual",
-+ "TEXT_OPT_COIN": "MONEDAS (MANTENER B)",
-+ "TEXT_OPT_HOVER": "FLOTAR",
-+ "TEXT_OPT_MOON": "GRAVEDAD LUNAR",
-+ "TEXT_OPT_RUN": "VELOCIDAD DE MOVIMIENTO",
-+ "TEXT_OPT_NDB": "SIN BARRERAS DE MUERTE",
-+ "TEXT_OPT_JUMP": "TODOS LOS SALTOS MAS ALTOS",
-+ "TEXT_OPT_SPDDPS": "VELOCIMETRO",
-+ "TEXT_OPT_TPF": "FLOTAR EN POSE T",
-+ "TEXT_OPT_JB": "LISTA DE CANCIONES",
-+ "TEXT_OPT_JBC": "REPRODUCIR CANCION",
-+ "TEXT_OPT_QUIKEND": "FINAL RAPIDO",
-+ "TEXT_OPT_HURT": "DAŅAR A MARIO L + A",
-+ "TEXT_OPT_CANN": "CAŅON DONDE SEA L + C UP",
-+ "TEXT_OPT_AWK": "SALTO DE PARED AUTOMATICO",
-+ "TEXT_OPT_SHELL": "OBTENER CAPARAZON L + R",
-+ "TEXT_OPT_BOB": "OBTENER BOBOMB L + B",
-+ "TEXT_OPT_SPAMBA": "SPAMBA L + Z",
-+ "TEXT_OPT_SWIM": "NADADO RAPIDO",
-+ "TEXT_OPT_WING_CAP": "OBTENER GORRA ALADA",
-+ "TEXT_OPT_METAL_CAP": "OBTENER GORRA METALICA",
-+ "TEXT_OPT_VANISH_CAP": "OBTENER GORRA INVISIBLE",
-+ "TEXT_OPT_REMOVE_CAP": "REMOVER GORRA",
-+ "TEXT_OPT_NORMAL_CAP": "REINICIAR GORRA",
-+ "TEXT_OPT_BLJ": "BLJ DONDE SEA",
-+ "TEXT_OPT_PAC": "JUGAR COMO",
-+ "TEXT_OPT_TRIPLE": "TODOS LOS SALTOS SON TRIPLE",
-+ "TEXT_OPT_FLY": "VOLADOR",
-+ "TEXT_OPT_NOB": "SIN BORDES",
-+ "TEXT_OPT_FLJ": "FORWARD LONG JUMP",
-+ "TEXT_OPT_TS": "FRENAR EL TIEMPO",
-+ "TEXT_OPT_COIN1": "APAGADO",
-+ "TEXT_OPT_COIN2": "MONEDA",
-+ "TEXT_OPT_COIN3": "MONEDA AZUL",
-+ "TEXT_OPT_COIN4": "MONEDA ROJA",
-+ "TEXT_OPT_SS1": "NORMAL",
-+ "TEXT_OPT_SS2": "LENTO",
-+ "TEXT_OPT_SS3": "MAS LENTO",
-+ "TEXT_OPT_SS4": "RAPIDO",
-+ "TEXT_OPT_SS5": "MAS RAPIDO",
-+ "TEXT_OPT_PA1": "DESACTIVADO",
-+ "TEXT_OPT_PA2": "BOBOMB NEGRA",
-+ "TEXT_OPT_PA3": "BOBOMB ROSA",
-+ "TEXT_OPT_PA4": "GOOMBA",
-+ "TEXT_OPT_PA5": "CAPARAZON",
-+ "TEXT_OPT_PA6": "CHUCKYA",
-+ "TEXT_OPT_PA7": "FLYGUY",
-+ "TEXT_OPT_PA8": "POR NIVEL",
-+ "TEXT_OPT_SEQ1": "INTRO",
-+ "TEXT_OPT_SEQ2": "BOB OMB BATTLEFIELD",
-+ "TEXT_OPT_SEQ3": "CASTILLO",
-+ "TEXT_OPT_SEQ4": "DIRE DIRE DOCKS",
-+ "TEXT_OPT_SEQ5": "LETHAL LAVA LAND",
-+ "TEXT_OPT_SEQ6": "BATALLA CON BOWSER",
-+ "TEXT_OPT_SEQ7": "COOL COOL MOUNTAIN",
-+ "TEXT_OPT_SEQ8": "SLIDE",
-+ "TEXT_OPT_SEQ9": "BIG BOO'S HAUNT",
-+ "TEXT_OPT_SEQ10": "WET DRY WORLD",
-+ "TEXT_OPT_SEQ11": "KOOPA ROAD",
-+ "TEXT_OPT_SEQ12": "BOWSER FINAL",
-+ "TEXT_OPT_SEQ13": "TITULOS",
-+ "TEXT_OPT_SEQ14": "PARTIDAS GUARDADAS",
-+ "TEXT_OPT_SEQ15": "GORRA ALADA",
-+ "TEXT_OPT_SEQ16": "GORRA METALICA",
-+ "TEXT_OPT_SEQ17": "JEFE",
-+ "TEXT_OPT_SEQ18": "CARRUSEL",
-+ "TEXT_OPT_SEQ19": "CREDITOS",
-+ "TEXT_OPT_HURTCHT1": "DESACTIVADO",
-+ "TEXT_OPT_HURTCHT2": "QUEMAR",
-+ "TEXT_OPT_HURTCHT3": "SHOCK",
-+ "TEXT_OPT_HURTCHT4": "1 HP",
-+ "TEXT_OPT_SPAMCHT1": "DESACTIVADO",
-+ "TEXT_OPT_SPAMCHT2": "AMP",
-+ "TEXT_OPT_SPAMCHT3": "SWITCH DE MONEDA AZUL",
-+ "TEXT_OPT_SPAMCHT4": "BOLA DE BOLOS",
-+ "TEXT_OPT_SPAMCHT5": "CAJA ROMPIBLE",
-+ "TEXT_OPT_SPAMCHT6": "MINI CAJA ROMPIBLE",
-+ "TEXT_OPT_SPAMCHT7": "CAJA SALTARINA",
-+ "TEXT_OPT_SPAMCHT8": "PLATAFORMA A CUADROS",
-+ "TEXT_OPT_SPAMCHT9": "CHUCKYA",
-+ "TEXT_OPT_SPAMCHT10": "FLYGUY",
-+ "TEXT_OPT_SPAMCHT11": "GOOMBAS",
-+ "TEXT_OPT_SPAMCHT12": "CORAZON",
-+ "TEXT_OPT_SPAMCHT13": "CAJA DE METAL",
-+ "TEXT_OPT_SPAMCHT14": "SWITCH PURPURA",
-+ "TEXT_OPT_BLJCHT1": "DESACTIVADO",
-+ "TEXT_OPT_BLJCHT2": "ACTIVADO",
-+ "TEXT_OPT_BLJCHT3": "ACTIVADO - BOOST: 1",
-+ "TEXT_OPT_BLJCHT4": "ACTIVADO - BOOST: 2",
-+ "TEXT_OPT_BLJCHT5": "ACTIVADO - BOOST: 3",
-+ "TEXT_OPT_BLJCHT6": "ACTIVADO - BOOST: 4",
-+ "TEXT_OPT_BLJCHT7": "ACTIVADO - BOOST: 5",
-+ "TEXT_OPT_BLJCHT8": "FUEGO RAPIDO",
-+ "TEXT_OPT_BLJCHT9": "FUEGO RAPIDO - BOOST: 1",
-+ "TEXT_OPT_BLJCHT10": "FUEGO RAPIDO - BOOST: 2",
-+ "TEXT_OPT_BLJCHT11": "FUEGO RAPIDO - BOOST: 3",
-+ "TEXT_OPT_BLJCHT12": "FUEGO RAPIDO - BOOST: 4",
-+ "TEXT_OPT_BLJCHT13": "FUEGO RAPIDO - BOOST: 5"
- },
- "strings": {
- "TEXT_ZERO": "0",
-diff --git a/texts/ES_la.json b/texts/ES_la.json
-index 83a65f99..f2463906 100644
---- a/texts/ES_la.json
-+++ b/texts/ES_la.json
-@@ -3393,7 +3393,104 @@
- "TEXT_OPT_CHEAT8": "Mario Gigante",
- "TEXT_OPT_CHEAT9": "Mario Peque{00241}o",
- "TEXT_OPT_GAME": "JUEGO",
-- "TEXT_OPT_LANGUAGE": "Idioma actual"
-+ "TEXT_OPT_LANGUAGE": "Idioma actual",
-+ "TEXT_OPT_COIN": "MONEDAS (MANTENER B)",
-+ "TEXT_OPT_HOVER": "FLOTAR",
-+ "TEXT_OPT_MOON": "GRAVEDAD LUNAR",
-+ "TEXT_OPT_RUN": "VELOCIDAD DE MOVIMIENTO",
-+ "TEXT_OPT_NDB": "SIN BARRERAS DE MUERTE",
-+ "TEXT_OPT_JUMP": "TODOS LOS SALTOS MAS ALTOS",
-+ "TEXT_OPT_SPDDPS": "VELOCIMETRO",
-+ "TEXT_OPT_TPF": "FLOTAR EN POSE T",
-+ "TEXT_OPT_JB": "LISTA DE CANCIONES",
-+ "TEXT_OPT_JBC": "REPRODUCIR CANCION",
-+ "TEXT_OPT_QUIKEND": "FINAL RAPIDO",
-+ "TEXT_OPT_HURT": "DAŅAR A MARIO L + A",
-+ "TEXT_OPT_CANN": "CAŅON DONDE SEA L + C UP",
-+ "TEXT_OPT_AWK": "SALTO DE PARED AUTOMATICO",
-+ "TEXT_OPT_SHELL": "OBTENER CAPARAZON L + R",
-+ "TEXT_OPT_BOB": "OBTENER BOBOMB L + B",
-+ "TEXT_OPT_SPAMBA": "SPAMBA L + Z",
-+ "TEXT_OPT_SWIM": "NADADO RAPIDO",
-+ "TEXT_OPT_WING_CAP": "OBTENER GORRA ALADA",
-+ "TEXT_OPT_METAL_CAP": "OBTENER GORRA METALICA",
-+ "TEXT_OPT_VANISH_CAP": "OBTENER GORRA INVISIBLE",
-+ "TEXT_OPT_REMOVE_CAP": "REMOVER GORRA",
-+ "TEXT_OPT_NORMAL_CAP": "REINICIAR GORRA",
-+ "TEXT_OPT_BLJ": "BLJ DONDE SEA",
-+ "TEXT_OPT_PAC": "JUGAR COMO",
-+ "TEXT_OPT_TRIPLE": "TODOS LOS SALTOS SON TRIPLE",
-+ "TEXT_OPT_FLY": "VOLADOR",
-+ "TEXT_OPT_NOB": "SIN BORDES",
-+ "TEXT_OPT_FLJ": "FORWARD LONG JUMP",
-+ "TEXT_OPT_TS": "FRENAR EL TIEMPO",
-+ "TEXT_OPT_COIN1": "APAGADO",
-+ "TEXT_OPT_COIN2": "MONEDA",
-+ "TEXT_OPT_COIN3": "MONEDA AZUL",
-+ "TEXT_OPT_COIN4": "MONEDA ROJA",
-+ "TEXT_OPT_SS1": "NORMAL",
-+ "TEXT_OPT_SS2": "LENTO",
-+ "TEXT_OPT_SS3": "MAS LENTO",
-+ "TEXT_OPT_SS4": "RAPIDO",
-+ "TEXT_OPT_SS5": "MAS RAPIDO",
-+ "TEXT_OPT_PA1": "DESACTIVADO",
-+ "TEXT_OPT_PA2": "BOBOMB NEGRA",
-+ "TEXT_OPT_PA3": "BOBOMB ROSA",
-+ "TEXT_OPT_PA4": "GOOMBA",
-+ "TEXT_OPT_PA5": "CAPARAZON",
-+ "TEXT_OPT_PA6": "CHUCKYA",
-+ "TEXT_OPT_PA7": "FLYGUY",
-+ "TEXT_OPT_PA8": "POR NIVEL",
-+ "TEXT_OPT_SEQ1": "INTRO",
-+ "TEXT_OPT_SEQ2": "BOB OMB BATTLEFIELD",
-+ "TEXT_OPT_SEQ3": "CASTILLO",
-+ "TEXT_OPT_SEQ4": "DIRE DIRE DOCKS",
-+ "TEXT_OPT_SEQ5": "LETHAL LAVA LAND",
-+ "TEXT_OPT_SEQ6": "BATALLA CON BOWSER",
-+ "TEXT_OPT_SEQ7": "COOL COOL MOUNTAIN",
-+ "TEXT_OPT_SEQ8": "SLIDE",
-+ "TEXT_OPT_SEQ9": "BIG BOO'S HAUNT",
-+ "TEXT_OPT_SEQ10": "WET DRY WORLD",
-+ "TEXT_OPT_SEQ11": "KOOPA ROAD",
-+ "TEXT_OPT_SEQ12": "BOWSER FINAL",
-+ "TEXT_OPT_SEQ13": "TITULOS",
-+ "TEXT_OPT_SEQ14": "PARTIDAS GUARDADAS",
-+ "TEXT_OPT_SEQ15": "GORRA ALADA",
-+ "TEXT_OPT_SEQ16": "GORRA METALICA",
-+ "TEXT_OPT_SEQ17": "JEFE",
-+ "TEXT_OPT_SEQ18": "CARRUSEL",
-+ "TEXT_OPT_SEQ19": "CREDITOS",
-+ "TEXT_OPT_HURTCHT1": "DESACTIVADO",
-+ "TEXT_OPT_HURTCHT2": "QUEMAR",
-+ "TEXT_OPT_HURTCHT3": "SHOCK",
-+ "TEXT_OPT_HURTCHT4": "1 HP",
-+ "TEXT_OPT_SPAMCHT1": "DESACTIVADO",
-+ "TEXT_OPT_SPAMCHT2": "AMP",
-+ "TEXT_OPT_SPAMCHT3": "SWITCH DE MONEDA AZUL",
-+ "TEXT_OPT_SPAMCHT4": "BOLA DE BOLOS",
-+ "TEXT_OPT_SPAMCHT5": "CAJA ROMPIBLE",
-+ "TEXT_OPT_SPAMCHT6": "MINI CAJA ROMPIBLE",
-+ "TEXT_OPT_SPAMCHT7": "CAJA SALTARINA",
-+ "TEXT_OPT_SPAMCHT8": "PLATAFORMA A CUADROS",
-+ "TEXT_OPT_SPAMCHT9": "CHUCKYA",
-+ "TEXT_OPT_SPAMCHT10": "FLYGUY",
-+ "TEXT_OPT_SPAMCHT11": "GOOMBAS",
-+ "TEXT_OPT_SPAMCHT12": "CORAZON",
-+ "TEXT_OPT_SPAMCHT13": "CAJA DE METAL",
-+ "TEXT_OPT_SPAMCHT14": "SWITCH PURPURA",
-+ "TEXT_OPT_BLJCHT1": "DESACTIVADO",
-+ "TEXT_OPT_BLJCHT2": "ACTIVADO",
-+ "TEXT_OPT_BLJCHT3": "ACTIVADO - BOOST: 1",
-+ "TEXT_OPT_BLJCHT4": "ACTIVADO - BOOST: 2",
-+ "TEXT_OPT_BLJCHT5": "ACTIVADO - BOOST: 3",
-+ "TEXT_OPT_BLJCHT6": "ACTIVADO - BOOST: 4",
-+ "TEXT_OPT_BLJCHT7": "ACTIVADO - BOOST: 5",
-+ "TEXT_OPT_BLJCHT8": "FUEGO RAPIDO",
-+ "TEXT_OPT_BLJCHT9": "FUEGO RAPIDO - BOOST: 1",
-+ "TEXT_OPT_BLJCHT10": "FUEGO RAPIDO - BOOST: 2",
-+ "TEXT_OPT_BLJCHT11": "FUEGO RAPIDO - BOOST: 3",
-+ "TEXT_OPT_BLJCHT12": "FUEGO RAPIDO - BOOST: 4",
-+ "TEXT_OPT_BLJCHT13": "FUEGO RAPIDO - BOOST: 5"
- },
- "strings": {
- "TEXT_ZERO": "0",
-diff --git a/texts/PL_pl.json b/texts/PL_pl.json
-index 75435fa2..ed277c53 100644
---- a/texts/PL_pl.json
-+++ b/texts/PL_pl.json
-@@ -3388,7 +3388,104 @@
- "TEXT_OPT_CHEAT8": "Huge Mario",
- "TEXT_OPT_CHEAT9": "Tiny Mario",
- "TEXT_OPT_GAME": "GRA",
-- "TEXT_OPT_LANGUAGE": "Jezyk"
-+ "TEXT_OPT_LANGUAGE": "Jezyk",
-+ "TEXT_OPT_COIN": "KODY MONETOWE (TRZYMAJ B)",
-+ "TEXT_OPT_HOVER": "TRYB UNOSZENIA",
-+ "TEXT_OPT_MOON": "GRAWITACJA KSIezYCA",
-+ "TEXT_OPT_RUN": "SZYBKOsc BIEGU",
-+ "TEXT_OPT_NDB": "BRAK BARIERY sMIERCI",
-+ "TEXT_OPT_JUMP": "WSZYSTKIE SKOKI WYzSZE",
-+ "TEXT_OPT_SPDDPS": "WYsWIETLACZ SZYBKOsCI",
-+ "TEXT_OPT_TPF": "UNOSZENIE W T POSE",
-+ "TEXT_OPT_JB": "LISTA PIOSENEK",
-+ "TEXT_OPT_JBC": "ZAGRAJ PIOSENKe",
-+ "TEXT_OPT_QUIKEND": "SZYBKIE ZAKOnCZENIE",
-+ "TEXT_OPT_HURT": "ZRAn MARIO",
-+ "TEXT_OPT_CANN": "ARMATA GDZIEKOLWIEK",
-+ "TEXT_OPT_AWK": "AUTO ODBIJANIE OD sCIAN",
-+ "TEXT_OPT_SHELL": "ZDOBaDz SKORUPe",
-+ "TEXT_OPT_BOB": "ZDOBaDz BOBOMBe",
-+ "TEXT_OPT_SPAMBA": "SPAMBA",
-+ "TEXT_OPT_SWIM": "SZYBKIE PlYWANIE",
-+ "TEXT_OPT_WING_CAP": "ZDOBaDz SKRZYDLATa CZAPKe",
-+ "TEXT_OPT_METAL_CAP": "ZDOBaDz METALOWa CZAPKe",
-+ "TEXT_OPT_VANISH_CAP": "ZDOBaDz NIEWIDZIALNa CZAPKe",
-+ "TEXT_OPT_REMOVE_CAP": "ZDEJMIJ CZAPKe",
-+ "TEXT_OPT_NORMAL_CAP": "ZRESETUJ CZAPKe",
-+ "TEXT_OPT_BLJ": "BLJ GDZIEKOLWIEK",
-+ "TEXT_OPT_PAC": "GRAJ JAKO",
-+ "TEXT_OPT_TRIPLE": "WSZYSTKIE SKOKI POTRoJNE",
-+ "TEXT_OPT_FLY": "LATACZ",
-+ "TEXT_OPT_NOB": "BEZ GRANIC",
-+ "TEXT_OPT_FLJ": "DALEKI SKOK DO PRZODU",
-+ "TEXT_OPT_TS": "ZATRZYMANIE CZASU",
-+ "TEXT_OPT_COIN1": "WYlaCZONY",
-+ "TEXT_OPT_COIN2": "MONETA",
-+ "TEXT_OPT_COIN3": "NIEBIESKA MONETA",
-+ "TEXT_OPT_COIN4": "CZERWONA MONETA",
-+ "TEXT_OPT_SS1": "NORMALNIE",
-+ "TEXT_OPT_SS2": "WOLNO",
-+ "TEXT_OPT_SS3": "WOLNIEJ",
-+ "TEXT_OPT_SS4": "SZYBKO",
-+ "TEXT_OPT_SS5": "SZYBCIEJ",
-+ "TEXT_OPT_PA1": "WYlaCZONY",
-+ "TEXT_OPT_PA2": "CZARNA BOBOMBA",
-+ "TEXT_OPT_PA3": "RozOWA BOBOMBA",
-+ "TEXT_OPT_PA4": "GOOMBA",
-+ "TEXT_OPT_PA5": "SKORUPA KOOPY",
-+ "TEXT_OPT_PA6": "CHUCKYA",
-+ "TEXT_OPT_PA7": "FLYGUY",
-+ "TEXT_OPT_PA8": "CO ETAP",
-+ "TEXT_OPT_SEQ1": "WSTeP",
-+ "TEXT_OPT_SEQ2": "TRAWA",
-+ "TEXT_OPT_SEQ3": "ZAMEK",
-+ "TEXT_OPT_SEQ4": "WODA",
-+ "TEXT_OPT_SEQ5": "GORaCE",
-+ "TEXT_OPT_SEQ6": "BOWSER",
-+ "TEXT_OPT_SEQ7": "sNIEG",
-+ "TEXT_OPT_SEQ8": "ZJEzDzALNIA",
-+ "TEXT_OPT_SEQ9": "STRASZNE",
-+ "TEXT_OPT_SEQ10": "PODZIEMIA",
-+ "TEXT_OPT_SEQ11": "sCIEzKA KOOPY",
-+ "TEXT_OPT_SEQ12": "OSTATECZNY BOWSER",
-+ "TEXT_OPT_SEQ13": "EKRAN TYTUlOWY",
-+ "TEXT_OPT_SEQ14": "WYBoR PLIKoW",
-+ "TEXT_OPT_SEQ15": "WZMOCNIENIE",
-+ "TEXT_OPT_SEQ16": "METALOWA CZAPKA",
-+ "TEXT_OPT_SEQ17": "BOSS",
-+ "TEXT_OPT_SEQ18": "KARUZELA",
-+ "TEXT_OPT_SEQ19": "NAPISY",
-+ "TEXT_OPT_HURTCHT1": "WYlaCZONY",
-+ "TEXT_OPT_HURTCHT2": "PODPALENIE",
-+ "TEXT_OPT_HURTCHT3": "ELEKTROWSTRZaS",
-+ "TEXT_OPT_HURTCHT4": "JEDEN PUNKT zYCIA",
-+ "TEXT_OPT_SPAMCHT1": "WYlaCZONY",
-+ "TEXT_OPT_SPAMCHT2": "AMP",
-+ "TEXT_OPT_SPAMCHT3": "PRZElaCZNIK NIEBIESKICH MONET",
-+ "TEXT_OPT_SPAMCHT4": "KULA DO KReGLI",
-+ "TEXT_OPT_SPAMCHT5": "ZNISZCZALNE PUDlO",
-+ "TEXT_OPT_SPAMCHT6": "ZNISZCZALNE PUDlO MAlE",
-+ "TEXT_OPT_SPAMCHT7": "SKACZaCE PUDlO",
-+ "TEXT_OPT_SPAMCHT8": "PLATFORMA Z SZACHOWNICa",
-+ "TEXT_OPT_SPAMCHT9": "CHUCKYA",
-+ "TEXT_OPT_SPAMCHT10": "FLYGUY",
-+ "TEXT_OPT_SPAMCHT11": "GOOMBY",
-+ "TEXT_OPT_SPAMCHT12": "SERCE",
-+ "TEXT_OPT_SPAMCHT13": "METALOWE PUDlO",
-+ "TEXT_OPT_SPAMCHT14": "FIOLETOWY PRZElaCZNIK",
-+ "TEXT_OPT_BLJCHT1": "WYlaCZONY",
-+ "TEXT_OPT_BLJCHT2": "WlaCZONY",
-+ "TEXT_OPT_BLJCHT3": "WlaCZONY - PReDKOsc: 1",
-+ "TEXT_OPT_BLJCHT4": "WlaCZONY - PReDKOsc: 2",
-+ "TEXT_OPT_BLJCHT5": "WlaCZONY - PReDKOsc: 3",
-+ "TEXT_OPT_BLJCHT6": "WlaCZONY - PReDKOsc: 4",
-+ "TEXT_OPT_BLJCHT7": "WlaCZONY - PReDKOsc: 5",
-+ "TEXT_OPT_BLJCHT8": "NACISKAJ A",
-+ "TEXT_OPT_BLJCHT9": "NACISKAJ A - PReDKOsc: 1",
-+ "TEXT_OPT_BLJCHT10": "NACISKAJ A - PReDKOsc: 2",
-+ "TEXT_OPT_BLJCHT11": "NACISKAJ A - PReDKOsc: 3",
-+ "TEXT_OPT_BLJCHT12": "NACISKAJ A - PReDKOsc: 4",
-+ "TEXT_OPT_BLJCHT13": "NACISKAJ A - PReDKOsc: 5"
- },
- "strings": {
- "TEXT_ZERO": "0",
-diff --git a/texts/PT_br.json b/texts/PT_br.json
-index 48ac695a..dd899e24 100644
---- a/texts/PT_br.json
-+++ b/texts/PT_br.json
-@@ -3346,7 +3346,104 @@
- "TEXT_OPT_CHEAT8": "Huge Mario",
- "TEXT_OPT_CHEAT9": "Tiny Mario",
- "TEXT_OPT_GAME": "JOGO",
-- "TEXT_OPT_LANGUAGE": "Idioma atual"
-+ "TEXT_OPT_LANGUAGE": "Idioma atual",
-+ "TEXT_OPT_COIN": "COIN CHEATS (HOLD B)",
-+ "TEXT_OPT_HOVER": "HOVER MODE",
-+ "TEXT_OPT_MOON": "MOON GRAVITY",
-+ "TEXT_OPT_RUN": "RUN SPEED",
-+ "TEXT_OPT_NDB": "NO DEATH BARRIER",
-+ "TEXT_OPT_JUMP": "ALL JUMPS HIGHER",
-+ "TEXT_OPT_SPDDPS": "SPEED DISPLAY",
-+ "TEXT_OPT_TPF": "T POSE FLOAT",
-+ "TEXT_OPT_JB": "SONG LIST",
-+ "TEXT_OPT_JBC": "PLAY SONG",
-+ "TEXT_OPT_QUIKEND": "QUICK ENDING",
-+ "TEXT_OPT_HURT": "HURT MARIO L + A",
-+ "TEXT_OPT_CANN": "CANNON ANYWHERE L + C UP",
-+ "TEXT_OPT_AWK": "AUTOWALLKICK",
-+ "TEXT_OPT_SHELL": "GET SHELL L + R",
-+ "TEXT_OPT_BOB": "GET BOBOMB L + B",
-+ "TEXT_OPT_SPAMBA": "SPAMBA L + Z",
-+ "TEXT_OPT_SWIM": "QUICK SWIM",
-+ "TEXT_OPT_WING_CAP": "GET WING CAP",
-+ "TEXT_OPT_METAL_CAP": "GET METAL CAP",
-+ "TEXT_OPT_VANISH_CAP": "GET VANISH CAP",
-+ "TEXT_OPT_REMOVE_CAP": "REMOVE CAP",
-+ "TEXT_OPT_NORMAL_CAP": "RESET CAP",
-+ "TEXT_OPT_BLJ": "BLJ ANYWHERE",
-+ "TEXT_OPT_PAC": "PLAY AS",
-+ "TEXT_OPT_TRIPLE": "ALL JUMPS TRIPLE",
-+ "TEXT_OPT_FLY": "FLYER",
-+ "TEXT_OPT_NOB": "NO BOUNDS",
-+ "TEXT_OPT_FLJ": "FORWARD LONG JUMP",
-+ "TEXT_OPT_TS": "TIME STOP",
-+ "TEXT_OPT_COIN1": "OFF",
-+ "TEXT_OPT_COIN2": "COIN",
-+ "TEXT_OPT_COIN3": "BLUE COIN",
-+ "TEXT_OPT_COIN4": "RED COIN",
-+ "TEXT_OPT_SS1": "NORMAL",
-+ "TEXT_OPT_SS2": "SLOW",
-+ "TEXT_OPT_SS3": "SLOWER",
-+ "TEXT_OPT_SS4": "FAST",
-+ "TEXT_OPT_SS5": "FASTER",
-+ "TEXT_OPT_PA1": "DISABLED",
-+ "TEXT_OPT_PA2": "BLACK BOBOMB",
-+ "TEXT_OPT_PA3": "PINK BOBOMB",
-+ "TEXT_OPT_PA4": "GOOMBA",
-+ "TEXT_OPT_PA5": "KAPPA SHELL",
-+ "TEXT_OPT_PA6": "CHUCKYA",
-+ "TEXT_OPT_PA7": "FLYGUY",
-+ "TEXT_OPT_PA8": "PER LEVEL",
-+ "TEXT_OPT_SEQ1": "INTRO",
-+ "TEXT_OPT_SEQ2": "GRASS",
-+ "TEXT_OPT_SEQ3": "CASTLE",
-+ "TEXT_OPT_SEQ4": "WATER",
-+ "TEXT_OPT_SEQ5": "HOT",
-+ "TEXT_OPT_SEQ6": "BOWSER",
-+ "TEXT_OPT_SEQ7": "SNOW",
-+ "TEXT_OPT_SEQ8": "SLIDE",
-+ "TEXT_OPT_SEQ9": "SPOOKY",
-+ "TEXT_OPT_SEQ10": "UNDERGROUND",
-+ "TEXT_OPT_SEQ11": "KOOPA ROAD",
-+ "TEXT_OPT_SEQ12": "FINAL BOWSER",
-+ "TEXT_OPT_SEQ13": "TITLE",
-+ "TEXT_OPT_SEQ14": "FILE SELECT",
-+ "TEXT_OPT_SEQ15": "POWERUP",
-+ "TEXT_OPT_SEQ16": "METAL CAP",
-+ "TEXT_OPT_SEQ17": "BOSS",
-+ "TEXT_OPT_SEQ18": "MERRYGOROUND",
-+ "TEXT_OPT_SEQ19": "CREDITS",
-+ "TEXT_OPT_HURTCHT1": "DISABLED",
-+ "TEXT_OPT_HURTCHT2": "BURN",
-+ "TEXT_OPT_HURTCHT3": "SHOCK",
-+ "TEXT_OPT_HURTCHT4": "ONE HP",
-+ "TEXT_OPT_SPAMCHT1": "DISABLED",
-+ "TEXT_OPT_SPAMCHT2": "AMP",
-+ "TEXT_OPT_SPAMCHT3": "BLUE COIN SWITCH",
-+ "TEXT_OPT_SPAMCHT4": "BOWLING BALL",
-+ "TEXT_OPT_SPAMCHT5": "BREAKABLE BOX",
-+ "TEXT_OPT_SPAMCHT6": "BREAKABLE BOX SMALL",
-+ "TEXT_OPT_SPAMCHT7": "JUMPING BOX",
-+ "TEXT_OPT_SPAMCHT8": "CHECKERBOARD PLATFORM",
-+ "TEXT_OPT_SPAMCHT9": "CHUCKYA",
-+ "TEXT_OPT_SPAMCHT10": "FLYGUY",
-+ "TEXT_OPT_SPAMCHT11": "GOOMBAS",
-+ "TEXT_OPT_SPAMCHT12": "HEART",
-+ "TEXT_OPT_SPAMCHT13": "METAL BOX",
-+ "TEXT_OPT_SPAMCHT14": "PURPLE SWITCH",
-+ "TEXT_OPT_BLJCHT1": "DISABLED",
-+ "TEXT_OPT_BLJCHT2": "ENABLED",
-+ "TEXT_OPT_BLJCHT3": "ENABLED - BOOST: 1",
-+ "TEXT_OPT_BLJCHT4": "ENABLED - BOOST: 2",
-+ "TEXT_OPT_BLJCHT5": "ENABLED - BOOST: 3",
-+ "TEXT_OPT_BLJCHT6": "ENABLED - BOOST: 4",
-+ "TEXT_OPT_BLJCHT7": "ENABLED - BOOST: 5",
-+ "TEXT_OPT_BLJCHT8": "RAPID FIRE",
-+ "TEXT_OPT_BLJCHT9": "RAPID FIRE - BOOST: 1",
-+ "TEXT_OPT_BLJCHT10": "RAPID FIRE - BOOST: 2",
-+ "TEXT_OPT_BLJCHT11": "RAPID FIRE - BOOST: 3",
-+ "TEXT_OPT_BLJCHT12": "RAPID FIRE - BOOST: 4",
-+ "TEXT_OPT_BLJCHT13": "RAPID FIRE - BOOST: 5"
- },
- "strings": {
- "TEXT_ZERO": "0",
diff --git a/include/text_options_strings.h.in b/include/text_options_strings.h.in
index 327e7705..537dfc83 100644
--- a/include/text_options_strings.h.in
+++ b/include/text_options_strings.h.in
@@ -60,6 +60,7 @@
#define TEXT_OPT_AUTO _("AUTO")
#define TEXT_OPT_HUD _("HUD")
#define TEXT_OPT_THREEPT _("THREE POINT")
+#define TEXT_OPT_DRAWDIST _("DRAW DISTANCE")
#define TEXT_OPT_APPLY _("APPLY")
#define TEXT_OPT_RESETWND _("RESET WINDOW")
@@ -133,6 +134,7 @@
#define TEXT_OPT_AUTO _("Auto")
#define TEXT_OPT_HUD _("HUD")
#define TEXT_OPT_THREEPT _("Three-point")
+#define TEXT_OPT_DRAWDIST _("Draw Distance")
#define TEXT_OPT_APPLY _("Apply")
#define TEXT_OPT_RESETWND _("Reset Window")
diff --git a/src/engine/behavior_script.c b/src/engine/behavior_script.c
index f61f2bf4..2848e977 100644
--- a/src/engine/behavior_script.c
+++ b/src/engine/behavior_script.c
@@ -13,6 +13,9 @@
#include "game/object_list_processor.h"
#include "graph_node.h"
#include "surface_collision.h"
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
// Macros for retrieving arguments from behavior scripts.
#define BHV_CMD_GET_1ST_U8(index) (u8)((gCurBhvCommand[index] >> 24) & 0xFF) // unused
@@ -999,7 +1002,7 @@ void cur_obj_update(void) {
if (!(objFlags & OBJ_FLAG_ACTIVE_FROM_AFAR)) {
// If the object has a render distance, check if it should be shown.
#ifndef NODRAWINGDISTANCE
- if (distanceFromMario > gCurrentObject->oDrawingDistance) {
+ if (distanceFromMario > gCurrentObject->oDrawingDistance * configDrawDistance / 100.0f) {
// Out of render distance, hide the object.
gCurrentObject->header.gfx.node.flags &= ~GRAPH_RENDER_ACTIVE;
gCurrentObject->activeFlags |= ACTIVE_FLAG_FAR_AWAY;
diff --git a/src/engine/surface_load.c b/src/engine/surface_load.c
index bdc81b5c..d112e545 100644
--- a/src/engine/surface_load.c
+++ b/src/engine/surface_load.c
@@ -15,6 +15,9 @@
#include "game/object_list_processor.h"
#include "surface_load.h"
#include "game/game_init.h"
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
s32 unused8038BE90;
@@ -796,7 +799,7 @@ void load_object_collision_model(void) {
}
#ifndef NODRAWINGDISTANCE
- if (marioDist < gCurrentObject->oDrawingDistance) {
+ if (marioDist < gCurrentObject->oDrawingDistance * configDrawDistance / 100.0f) {
#endif
gCurrentObject->header.gfx.node.flags |= GRAPH_RENDER_ACTIVE;
#ifndef NODRAWINGDISTANCE
diff --git a/src/game/behaviors/bub.inc.c b/src/game/behaviors/bub.inc.c
index 7bf71690..f8577d47 100644
--- a/src/game/behaviors/bub.inc.c
+++ b/src/game/behaviors/bub.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// bub.c.inc
// NOTE: These first set of functions spawn a school of bub depending on objF4's
@@ -9,7 +13,7 @@ void bub_spawner_act_0(void) {
s32 i;
s32 sp18 = o->oBirdChirpChirpUnkF4;
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 1500.0f) {
+ if (o->oDistanceToMario < 15 * configDrawDistance) {
#endif
for (i = 0; i < sp18; i++)
spawn_object(o, MODEL_BUB, bhvBub);
diff --git a/src/game/behaviors/chain_chomp.inc.c b/src/game/behaviors/chain_chomp.inc.c
index 9b9c3423..4d0af942 100644
--- a/src/game/behaviors/chain_chomp.inc.c
+++ b/src/game/behaviors/chain_chomp.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvChainChomp, bhvChainChompChainPart, bhvWoodenPost, and bhvChainChompGate.
@@ -54,7 +58,7 @@ static void chain_chomp_act_uninitialized(void) {
s32 i;
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 3000.0f) {
+ if (o->oDistanceToMario < 30 * configDrawDistance) {
#endif
segments = mem_pool_alloc(gObjectMemoryPool, 5 * sizeof(struct ChainSegment));
if (segments != NULL) {
@@ -364,7 +368,7 @@ static void chain_chomp_act_move(void) {
// Unload chain if mario is far enough
#ifndef NODRAWINGDISTANCE
- if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && o->oDistanceToMario > 4000.0f) {
+ if (o->oChainChompReleaseStatus == CHAIN_CHOMP_NOT_RELEASED && o->oDistanceToMario > 40 * configDrawDistance) {
o->oAction = CHAIN_CHOMP_ACT_UNLOAD_CHAIN;
o->oForwardVel = o->oVelY = 0.0f;
} else {
diff --git a/src/game/behaviors/cloud.inc.c b/src/game/behaviors/cloud.inc.c
index c1d708bf..ec5f7a90 100644
--- a/src/game/behaviors/cloud.inc.c
+++ b/src/game/behaviors/cloud.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvCloud and bhvCloudPart.
@@ -48,7 +52,7 @@ static void cloud_act_spawn_parts(void) {
*/
static void cloud_act_fwoosh_hidden(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2000.0f) {
+ if (o->oDistanceToMario < 20 * configDrawDistance) {
#endif
cur_obj_unhide();
o->oAction = CLOUD_ACT_SPAWN_PARTS;
@@ -63,7 +67,7 @@ static void cloud_act_fwoosh_hidden(void) {
*/
static void cloud_fwoosh_update(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 2500.0f) {
+ if (o->oDistanceToMario > 25 * configDrawDistance) {
o->oAction = CLOUD_ACT_UNLOAD;
} else {
#endif
diff --git a/src/game/behaviors/coin.inc.c b/src/game/behaviors/coin.inc.c
index 9b7099de..5370f2d6 100644
--- a/src/game/behaviors/coin.inc.c
+++ b/src/game/behaviors/coin.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// coin.c.inc
struct ObjectHitbox sYellowCoinHitbox = {
@@ -185,7 +189,7 @@ void bhv_coin_formation_loop(void) {
switch (o->oAction) {
case 0:
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2000.0f) {
+ if (o->oDistanceToMario < 20 * configDrawDistance) {
#endif
for (bitIndex = 0; bitIndex < 8; bitIndex++) {
if (!(o->oCoinUnkF4 & (1 << bitIndex)))
@@ -198,7 +202,7 @@ void bhv_coin_formation_loop(void) {
break;
case 1:
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 2100.0f)
+ if (o->oDistanceToMario > 21 * configDrawDistance)
o->oAction++;
#endif
break;
diff --git a/src/game/behaviors/enemy_lakitu.inc.c b/src/game/behaviors/enemy_lakitu.inc.c
index cacd732f..72553dcc 100644
--- a/src/game/behaviors/enemy_lakitu.inc.c
+++ b/src/game/behaviors/enemy_lakitu.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvEnemyLakitu.
@@ -25,7 +29,7 @@ static struct ObjectHitbox sEnemyLakituHitbox = {
*/
static void enemy_lakitu_act_uninitialized(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2000.0f) {
+ if (o->oDistanceToMario < 20 * configDrawDistance) {
#endif
spawn_object_relative_with_scale(CLOUD_BP_LAKITU_CLOUD, 0, 0, 0, 2.0f, o, MODEL_MIST, bhvCloud);
diff --git a/src/game/behaviors/fish.inc.c b/src/game/behaviors/fish.inc.c
index d169ecf7..f9572725 100644
--- a/src/game/behaviors/fish.inc.c
+++ b/src/game/behaviors/fish.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* @file fish.inc.c
* Implements behaviour and spawning for fish located in the Secret Aquarium and other levels.
@@ -43,7 +47,7 @@ void fish_act_spawn(void) {
* Fish moves at random with a max-range of 700.0f.
*/
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < minDistToMario || gCurrLevelNum == LEVEL_SA) {
+ if (o->oDistanceToMario < minDistToMario * configDrawDistance / 100 || gCurrLevelNum == LEVEL_SA) {
#endif
for (i = 0; i < schoolQuantity; i++) {
fishObject = spawn_object(o, model, bhvFish);
@@ -64,7 +68,7 @@ void fish_act_spawn(void) {
void fish_act_respawn(void) {
#ifndef NODRAWINGDISTANCE
if (gCurrLevelNum != LEVEL_SA) {
- if (gMarioObject->oPosY - o->oPosY > 2000.0f) {
+ if (gMarioObject->oPosY - o->oPosY > 20 * configDrawDistance) {
o->oAction = FISH_ACT_RESPAWN;
}
}
diff --git a/src/game/behaviors/goomba.inc.c b/src/game/behaviors/goomba.inc.c
index bf47dda1..6fce18b3 100644
--- a/src/game/behaviors/goomba.inc.c
+++ b/src/game/behaviors/goomba.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvGoomba and bhvGoombaTripletSpawner,
@@ -79,7 +83,7 @@ void bhv_goomba_triplet_spawner_update(void) {
// spawn them
if (o->oAction == GOOMBA_TRIPLET_SPAWNER_ACT_UNLOADED) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 3000.0f) {
+ if (o->oDistanceToMario < 30 * configDrawDistance) {
#endif
// The spawner is capable of spawning more than 3 goombas, but this
// is not used in the game
@@ -102,7 +106,7 @@ void bhv_goomba_triplet_spawner_update(void) {
o->oAction += 1;
#ifndef NODRAWINGDISTANCE
}
- } else if (o->oDistanceToMario > 4000.0f) {
+ } else if (o->oDistanceToMario > 40 * configDrawDistance) {
// If mario is too far away, enter the unloaded action. The goombas
// will detect this and unload themselves
o->oAction = GOOMBA_TRIPLET_SPAWNER_ACT_UNLOADED;
diff --git a/src/game/behaviors/heave_ho.inc.c b/src/game/behaviors/heave_ho.inc.c
index 1a247607..1da0b243 100644
--- a/src/game/behaviors/heave_ho.inc.c
+++ b/src/game/behaviors/heave_ho.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// heave_ho.c.inc
s16 D_8032F460[][2] = { { 30, 0 }, { 42, 1 }, { 52, 0 }, { 64, 1 }, { 74, 0 },
@@ -73,7 +77,7 @@ void heave_ho_act_3(void) {
void heave_ho_act_0(void) {
#ifndef NODRAWINGDISTANCE
- if (find_water_level(o->oPosX, o->oPosZ) < o->oPosY && o->oDistanceToMario < 4000.0f) {
+ if (find_water_level(o->oPosX, o->oPosZ) < o->oPosY && o->oDistanceToMario < 40 * configDrawDistance) {
#else
if (find_water_level(o->oPosX, o->oPosZ) < (o->oPosY - 50.0f)) {
#endif
diff --git a/src/game/behaviors/king_bobomb.inc.c b/src/game/behaviors/king_bobomb.inc.c
index 7942b2bb..6aec3d15 100644
--- a/src/game/behaviors/king_bobomb.inc.c
+++ b/src/game/behaviors/king_bobomb.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// king_bobomb.c.inc
// Copy of geo_update_projectile_pos_from_parent
@@ -296,7 +300,7 @@ void king_bobomb_move(void) {
cur_obj_call_action_function(sKingBobombActions);
exec_anim_sound_state(sKingBobombSoundStates);
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 5000.0f)
+ if (o->oDistanceToMario < 50 * configDrawDistance)
#endif
cur_obj_enable_rendering();
#ifndef NODRAWINGDISTANCE
diff --git a/src/game/behaviors/lll_floating_wood_piece.inc.c b/src/game/behaviors/lll_floating_wood_piece.inc.c
index 7994e2d9..9e28fca4 100644
--- a/src/game/behaviors/lll_floating_wood_piece.inc.c
+++ b/src/game/behaviors/lll_floating_wood_piece.inc.c
@@ -1,5 +1,9 @@
// lll_floating_wood_piece.c.inc
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
void bhv_lll_wood_piece_loop(void) {
if (o->oTimer == 0)
o->oPosY -= 100.0f;
@@ -15,7 +19,7 @@ void bhv_lll_floating_wood_bridge_loop(void) {
switch (o->oAction) {
case 0:
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2500.0f) {
+ if (o->oDistanceToMario < 25 * configDrawDistance) {
#endif
for (i = 1; i < 4; i++) {
sp3C = spawn_object_relative(0, (i - 2) * 300, 0, 0, o, MODEL_LLL_WOOD_BRIDGE,
@@ -29,7 +33,7 @@ void bhv_lll_floating_wood_bridge_loop(void) {
break;
case 1:
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 2600.0f)
+ if (o->oDistanceToMario > 26 * configDrawDistance)
o->oAction = 2;
#endif
break;
diff --git a/src/game/behaviors/lll_rotating_hex_flame.inc.c b/src/game/behaviors/lll_rotating_hex_flame.inc.c
index fc707330..2c6bad00 100644
--- a/src/game/behaviors/lll_rotating_hex_flame.inc.c
+++ b/src/game/behaviors/lll_rotating_hex_flame.inc.c
@@ -1,5 +1,9 @@
// lll_rotating_hex_flame.c.inc
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
void bhv_lll_rotating_hex_flame_loop(void) {
f32 sp24 = o->oLllRotatingHexFlameUnkF4;
f32 sp20 = o->oLllRotatingHexFlameUnkF8;
@@ -31,7 +35,7 @@ void fire_bar_spawn_flames(s16 a0) {
void fire_bar_act_0(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 3000.0f)
+ if (o->oDistanceToMario < 30 * configDrawDistance)
#endif
o->oAction = 1;
}
@@ -48,7 +52,7 @@ void fire_bar_act_2(void) {
o->oAngleVelYaw = -0x100;
o->oMoveAngleYaw += o->oAngleVelYaw;
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 3200.0f)
+ if (o->oDistanceToMario > 32 * configDrawDistance)
o->oAction = 3;
#endif
}
diff --git a/src/game/behaviors/piranha_plant.inc.c b/src/game/behaviors/piranha_plant.inc.c
index 328f4518..f59d27ce 100644
--- a/src/game/behaviors/piranha_plant.inc.c
+++ b/src/game/behaviors/piranha_plant.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvPiranhaPlant.
* This controls Piranha Plants, which alternate between sleeping, attacking,
@@ -331,7 +335,7 @@ void bhv_piranha_plant_loop(void) {
#ifndef NODRAWINGDISTANCE
// In WF, hide all Piranha Plants once high enough up.
if (gCurrLevelNum == LEVEL_WF) {
- if (gMarioObject->oPosY > 3400.0f)
+ if (gMarioObject->oPosY > 34 * configDrawDistance)
cur_obj_hide();
else
cur_obj_unhide();
diff --git a/src/game/behaviors/pokey.inc.c b/src/game/behaviors/pokey.inc.c
index cfcc92c2..b19db8e6 100644
--- a/src/game/behaviors/pokey.inc.c
+++ b/src/game/behaviors/pokey.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior for bhvPokey and bhvPokeyBodyPart.
@@ -152,7 +156,7 @@ static void pokey_act_uninitialized(void) {
s16 partModel;
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2000.0f) {
+ if (o->oDistanceToMario < 20 * configDrawDistance) {
#endif
partModel = MODEL_POKEY_HEAD;
@@ -190,7 +194,7 @@ static void pokey_act_wander(void) {
if (o->oPokeyNumAliveBodyParts == 0) {
obj_mark_for_deletion(o);
#ifndef NODRAWINGDISTANCE
- } else if (o->oDistanceToMario > 2500.0f) {
+ } else if (o->oDistanceToMario > 25 * configDrawDistance) {
o->oAction = POKEY_ACT_UNLOAD_PARTS;
o->oForwardVel = 0.0f;
#endif
diff --git a/src/game/behaviors/sl_walking_penguin.inc.c b/src/game/behaviors/sl_walking_penguin.inc.c
index 59428acb..0599a16c 100644
--- a/src/game/behaviors/sl_walking_penguin.inc.c
+++ b/src/game/behaviors/sl_walking_penguin.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// sl_walking_penguin.c.inc
struct SLWalkingPenguinStep {
@@ -98,7 +102,7 @@ void bhv_sl_walking_penguin_loop(void) {
cur_obj_move_standard(-78);
#ifndef NODRAWINGDISTANCE
- if (!cur_obj_hide_if_mario_far_away_y(1000.0f))
+ if (!cur_obj_hide_if_mario_far_away_y(10 * configDrawDistance))
#endif
play_penguin_walking_sound(PENGUIN_WALK_BIG);
diff --git a/src/game/behaviors/snufit.inc.c b/src/game/behaviors/snufit.inc.c
index ae28672b..459dea73 100644
--- a/src/game/behaviors/snufit.inc.c
+++ b/src/game/behaviors/snufit.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
/**
* Behavior file for bhvSnufit and bhvSnufitBalls.
* Snufits are present in HMC and CotMC, and are the fly guy
@@ -181,7 +185,7 @@ void bhv_snufit_balls_loop(void) {
// If far from Mario or in a different room, despawn.
if ((o->activeFlags & ACTIVE_FLAG_IN_DIFFERENT_ROOM)
#ifndef NODRAWINGDISTANCE
- || (o->oTimer != 0 && o->oDistanceToMario > 1500.0f)
+ || (o->oTimer != 0 && o->oDistanceToMario > 15 * configDrawDistance)
#endif
){
obj_mark_for_deletion(o);
diff --git a/src/game/behaviors/triplet_butterfly.inc.c b/src/game/behaviors/triplet_butterfly.inc.c
index 5f971854..6309b488 100644
--- a/src/game/behaviors/triplet_butterfly.inc.c
+++ b/src/game/behaviors/triplet_butterfly.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
struct TripletButterflyActivationData {
s32 model;
const BehaviorScript *behavior;
@@ -55,7 +59,7 @@ static void triplet_butterfly_act_init(void) {
static void triplet_butterfly_act_wander(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 1500.0f) {
+ if (o->oDistanceToMario > 15 * configDrawDistance) {
obj_mark_for_deletion(o);
} else {
#endif
diff --git a/src/game/behaviors/water_bomb_cannon.inc.c b/src/game/behaviors/water_bomb_cannon.inc.c
index fb82e43c..ae242fa8 100644
--- a/src/game/behaviors/water_bomb_cannon.inc.c
+++ b/src/game/behaviors/water_bomb_cannon.inc.c
@@ -1,5 +1,9 @@
// water_bomb_cannon.inc.c
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
void bhv_bubble_cannon_barrel_loop(void) {
struct Object *val04;
@@ -39,7 +43,7 @@ void bhv_bubble_cannon_barrel_loop(void) {
void water_bomb_cannon_act_0(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 2000.0f) {
+ if (o->oDistanceToMario < 20 * configDrawDistance) {
#endif
spawn_object(o, MODEL_CANNON_BARREL, bhvCannonBarrelBubbles);
cur_obj_unhide();
@@ -53,7 +57,7 @@ void water_bomb_cannon_act_0(void) {
void water_bomb_cannon_act_1(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario > 2500.0f) {
+ if (o->oDistanceToMario > 25 * configDrawDistance) {
o->oAction = 2;
} else if (o->oBehParams2ndByte == 0) {
#else
diff --git a/src/game/behaviors/whirlpool.inc.c b/src/game/behaviors/whirlpool.inc.c
index 5aebebd2..04f9c6cf 100644
--- a/src/game/behaviors/whirlpool.inc.c
+++ b/src/game/behaviors/whirlpool.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// whirlpool.c.inc
static struct ObjectHitbox sWhirlpoolHitbox = {
@@ -36,7 +40,7 @@ void whirpool_orient_graph(void) {
void bhv_whirlpool_loop(void) {
#ifndef NODRAWINGDISTANCE
- if (o->oDistanceToMario < 5000.0f) {
+ if (o->oDistanceToMario < 50 * configDrawDistance) {
#endif
o->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
diff --git a/src/game/behaviors/whomp.inc.c b/src/game/behaviors/whomp.inc.c
index 1f3bcb7f..34c5a59f 100644
--- a/src/game/behaviors/whomp.inc.c
+++ b/src/game/behaviors/whomp.inc.c
@@ -1,3 +1,7 @@
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
+
// whomp.c.inc
void whomp_play_sfx_from_pound_animation(void) {
@@ -250,9 +254,9 @@ void bhv_whomp_loop(void) {
// o->oBehParams2ndByte here seems to be a flag
// indicating whether this is a normal or king whomp
if (o->oBehParams2ndByte != 0)
- cur_obj_hide_if_mario_far_away_y(2000.0f);
+ cur_obj_hide_if_mario_far_away_y(20 * configDrawDistance);
else
- cur_obj_hide_if_mario_far_away_y(1000.0f);
+ cur_obj_hide_if_mario_far_away_y(10 * configDrawDistance);
#endif
load_object_collision_model();
}
diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c
index 601c45f1..369853ba 100644
--- a/src/game/obj_behaviors.c
+++ b/src/game/obj_behaviors.c
@@ -27,6 +27,9 @@
#include "obj_behaviors.h"
#include "object_helpers.h"
#include "object_list_processor.h"
+#ifndef NODRAWINGDISTANCE
+#include "pc/configfile.h"
+#endif
#include "rendering_graph_node.h"
#include "save_file.h"
#include "spawn_object.h"
@@ -531,7 +534,7 @@ void set_object_visibility(struct Object *obj, s32 dist) {
f32 objZ = obj->oPosZ;
#ifndef NODRAWINGDISTANCE
- if (is_point_within_radius_of_mario(objX, objY, objZ, dist) == TRUE) {
+ if (is_point_within_radius_of_mario(objX, objY, objZ, dist * configDrawDistance / 100) == TRUE) {
#endif
obj->header.gfx.node.flags &= ~GRAPH_RENDER_INVISIBLE;
#ifndef NODRAWINGDISTANCE
diff --git a/src/game/options_menu.c b/src/game/options_menu.c
index c435fa97..1d4c143e 100644
--- a/src/game/options_menu.c
+++ b/src/game/options_menu.c
@@ -84,6 +84,7 @@ static const u8 optsVideoStr[][32] = {
{ TEXT_OPT_AUTO },
{ TEXT_OPT_HUD },
{ TEXT_OPT_THREEPT },
+ { TEXT_OPT_DRAWDIST },
{ TEXT_OPT_APPLY },
};
@@ -276,8 +277,11 @@ static struct Option optsVideo[] = {
DEF_OPT_TOGGLE( optsVideoStr[5], &configWindow.vsync ),
DEF_OPT_CHOICE( optsVideoStr[1], &configFiltering, filterChoices ),
DEF_OPT_TOGGLE( optsVideoStr[7], &configHUD ),
+#ifndef NODRAWINGDISTANCE
+ DEF_OPT_SCROLL( optsVideoStr[9], &configDrawDistance, 50, 509, 10 ),
+#endif
DEF_OPT_BUTTON( optsVideoStr[4], optvideo_reset_window ),
- DEF_OPT_BUTTON( optsVideoStr[9], optvideo_apply ),
+ DEF_OPT_BUTTON( optsVideoStr[10], optvideo_apply ),
};
static struct Option optsAudio[] = {
diff --git a/src/pc/configfile.c b/src/pc/configfile.c
index b2de88b5..25cf5254 100644
--- a/src/pc/configfile.c
+++ b/src/pc/configfile.c
@@ -97,6 +97,9 @@ bool configCameraMouse = false;
#endif
bool configSkipIntro = 0;
bool configHUD = true;
+#ifndef NODRAWINGDISTANCE
+unsigned int configDrawDistance = 100;
+#endif
#ifdef DISCORDRPC
bool configDiscordRPC = true;
#endif
@@ -109,6 +112,9 @@ static const struct ConfigOption options[] = {
{.name = "window_h", .type = CONFIG_TYPE_UINT, .uintValue = &configWindow.h},
{.name = "vsync", .type = CONFIG_TYPE_BOOL, .boolValue = &configWindow.vsync},
{.name = "texture_filtering", .type = CONFIG_TYPE_UINT, .uintValue = &configFiltering},
+ #ifndef NODRAWINGDISTANCE
+ {.name = "drawing_distance", .type = CONFIG_TYPE_UINT, .uintValue = &configDrawDistance},
+ #endif
{.name = "master_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMasterVolume},
{.name = "music_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configMusicVolume},
{.name = "sfx_volume", .type = CONFIG_TYPE_UINT, .uintValue = &configSfxVolume},
diff --git a/src/pc/configfile.h b/src/pc/configfile.h
index 72fcb464..77fe8932 100644
--- a/src/pc/configfile.h
+++ b/src/pc/configfile.h
@@ -61,6 +61,9 @@ extern bool configCameraMouse;
extern bool configCameraAnalog;
#endif
extern bool configHUD;
+#ifndef NODRAWINGDISTANCE
+extern unsigned int configDrawDistance;
+#endif
extern bool configSkipIntro;
#ifdef DISCORDRPC
extern bool configDiscordRPC;