summaryrefslogtreecommitdiffstats
path: root/src/extras
diff options
context:
space:
mode:
Diffstat (limited to 'src/extras')
-rw-r--r--src/extras/frontendoption.cpp298
-rw-r--r--src/extras/frontendoption.h162
2 files changed, 460 insertions, 0 deletions
diff --git a/src/extras/frontendoption.cpp b/src/extras/frontendoption.cpp
new file mode 100644
index 00000000..3214bbfd
--- /dev/null
+++ b/src/extras/frontendoption.cpp
@@ -0,0 +1,298 @@
+#include "common.h"
+
+#ifdef CUSTOM_FRONTEND_OPTIONS
+#include "frontendoption.h"
+#include "Text.h"
+
+int numCustomFrontendOptions = 0;
+FrontendOption *customFrontendOptions;
+
+int numCustomFrontendScreens = 0;
+FrontendScreen* customFrontendScreens;
+
+int numFrontendOptionReplacements = 0;
+CMenuScreen::CMenuEntry* frontendOptionReplacements;
+
+int lastOgScreen = MENUPAGES; // means no new pages
+
+int optionCursor = -2;
+int currentMenu;
+bool optionOverwrite = false;
+
+void ChangeScreen(int screen, int option, bool fadeIn)
+{
+ FrontEndMenuManager.m_nPrevScreen = FrontEndMenuManager.m_nCurrScreen;
+ FrontEndMenuManager.m_nCurrScreen = screen;
+ FrontEndMenuManager.m_nCurrOption = option;
+ if (fadeIn)
+ FrontEndMenuManager.m_nMenuFadeAlpha = 0;
+}
+
+void GoBack(bool fadeIn)
+{
+ int screen = !FrontEndMenuManager.m_bGameNotLoaded ?
+ aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_PreviousPage[0];
+ int option = !FrontEndMenuManager.m_bGameNotLoaded ?
+ aScreens[FrontEndMenuManager.m_nCurrScreen].m_ParentEntry[1] : aScreens[FrontEndMenuManager.m_nCurrScreen].m_ParentEntry[0];
+
+ FrontEndMenuManager.ThingsToDoBeforeGoingBack();
+
+ ChangeScreen(screen, option, fadeIn);
+}
+
+uint8
+GetNumberOfMenuOptions(int screen)
+{
+ uint8 Rows = 0;
+ for (int i = 0; i < NUM_MENUROWS; i++) {
+ if (aScreens[screen].m_aEntries[i].m_Action == MENUACTION_NOTHING)
+ break;
+
+ ++Rows;
+ }
+ return Rows;
+}
+
+uint8
+GetLastMenuScreen()
+{
+ uint8 page = -1;
+ for (int i = 0; i < MENUPAGES; i++) {
+ if (strcmp(aScreens[i].m_ScreenName, "") == 0 && aScreens[i].unk == 0)
+ break;
+
+ ++page;
+ }
+ return page;
+}
+
+// Used before populating options, but effective in InitialiseChangedLanguageSettings and debugmenu
+void
+RemoveCustomFrontendOptions()
+{
+ if (numCustomFrontendOptions != 0) {
+
+ for (int i = 0; i < MENUPAGES; i++) {
+ for (int j = 0; j < NUM_MENUROWS; j++) {
+ if (aScreens[i].m_aEntries[j].m_SaveSlot == SAVESLOT_CFO) {
+ int ogOptionId = customFrontendOptions[aScreens[i].m_aEntries[j].m_TargetMenu].ogOptionId;
+ if (ogOptionId == -1) {
+ int k;
+ for (k = j; k < NUM_MENUROWS - 1; k++) {
+ memcpy(&aScreens[i].m_aEntries[k], &aScreens[i].m_aEntries[k + 1], sizeof(CMenuScreen::CMenuEntry));
+ }
+ aScreens[i].m_aEntries[k].m_Action = MENUACTION_NOTHING;
+ aScreens[i].m_aEntries[k].m_SaveSlot = SAVESLOT_NONE;
+ aScreens[i].m_aEntries[k].m_EntryName[0] = '\0';
+ j--;
+ } else {
+ memcpy(&aScreens[i].m_aEntries[j], &frontendOptionReplacements[ogOptionId], sizeof(CMenuScreen::CMenuEntry));
+ }
+ }
+ }
+ }
+ free(customFrontendOptions);
+ numCustomFrontendOptions = 0;
+
+ if (numFrontendOptionReplacements != 0) {
+ free(frontendOptionReplacements);
+ numFrontendOptionReplacements = 0;
+ }
+ }
+
+ if (numCustomFrontendScreens == 0)
+ return;
+
+ for (int i = 0; i < MENUPAGES; i++) {
+ if (i > lastOgScreen) {
+ aScreens[i].m_ScreenName[0] = '\0';
+ aScreens[i].unk = 0;
+ }
+ }
+ free(customFrontendScreens);
+ numCustomFrontendScreens = 0;
+ lastOgScreen = MENUPAGES;
+}
+
+int8 RegisterNewScreen(char *name, int prevPage)
+{
+ if (lastOgScreen == MENUPAGES)
+ lastOgScreen = GetLastMenuScreen();
+
+ numCustomFrontendScreens++;
+ if (numCustomFrontendScreens == 1)
+ customFrontendScreens = (FrontendScreen*)malloc(5 * sizeof(FrontendScreen));
+ else if (numCustomFrontendScreens % 5 == 1)
+ customFrontendScreens = (FrontendScreen*)realloc(customFrontendScreens, (numCustomFrontendScreens + 4) * sizeof(FrontendScreen));
+
+ assert(customFrontendScreens != nil && "Custom frontend screens can't be allocated");
+
+ int id = lastOgScreen + numCustomFrontendScreens;
+ assert(id < MENUPAGES && "No room for new custom frontend screens! Increase MENUPAGES");
+ strncpy(aScreens[id].m_ScreenName, name, 8);
+ aScreens[id].m_PreviousPage[0] = aScreens[id].m_PreviousPage[1] = prevPage;
+ aScreens[id].unk = 1;
+ return id;
+}
+
+int8 RegisterNewOption()
+{
+ numCustomFrontendOptions++;
+ if (numCustomFrontendOptions == 1)
+ customFrontendOptions = (FrontendOption*)malloc(5 * sizeof(FrontendOption));
+ else if (numCustomFrontendOptions % 5 == 1)
+ customFrontendOptions = (FrontendOption*)realloc(customFrontendOptions, (numCustomFrontendOptions + 4) * sizeof(FrontendOption));
+
+ assert(customFrontendOptions != nil && "Custom frontend options can't be allocated");
+
+ uint8 numOptions = GetNumberOfMenuOptions(currentMenu);
+ uint8 curIdx;
+ if (optionCursor < 0) {
+ optionCursor = curIdx = numOptions + optionCursor + 1;
+ } else
+ curIdx = optionCursor;
+
+ if (!optionOverwrite) {
+ if (aScreens[currentMenu].m_aEntries[curIdx].m_Action != MENUACTION_NOTHING) {
+ for (int i = numOptions - 1; i >= curIdx; i--) {
+ memcpy(&aScreens[currentMenu].m_aEntries[i + 1], &aScreens[currentMenu].m_aEntries[i], sizeof(CMenuScreen::CMenuEntry));
+ }
+ }
+ }
+ optionCursor++;
+
+ if (optionOverwrite) {
+ numFrontendOptionReplacements++;
+ if (numFrontendOptionReplacements == 1)
+ frontendOptionReplacements = (CMenuScreen::CMenuEntry*)malloc(5 * sizeof(CMenuScreen::CMenuEntry));
+ else if (numFrontendOptionReplacements % 5 == 1)
+ frontendOptionReplacements = (CMenuScreen::CMenuEntry*)realloc(frontendOptionReplacements, (numFrontendOptionReplacements + 4) * sizeof(CMenuScreen::CMenuEntry));
+
+ memcpy(&frontendOptionReplacements[numFrontendOptionReplacements - 1], &aScreens[currentMenu].m_aEntries[curIdx], sizeof(CMenuScreen::CMenuEntry));
+ customFrontendOptions[numCustomFrontendOptions - 1].ogOptionId = numFrontendOptionReplacements - 1;
+ } else {
+ customFrontendOptions[numCustomFrontendOptions - 1].ogOptionId = -1;
+ }
+ customFrontendOptions[numCustomFrontendOptions - 1].screen = currentMenu;
+
+ aScreens[currentMenu].m_aEntries[curIdx].m_Action = MENUACTION_TRIGGERFUNC;
+ aScreens[currentMenu].m_aEntries[curIdx].m_SaveSlot = SAVESLOT_CFO;
+ aScreens[currentMenu].m_aEntries[curIdx].m_TargetMenu = numCustomFrontendOptions - 1;
+ aScreens[currentMenu].m_aEntries[curIdx].m_EntryName[0] = 1; // just something to fool it
+ return curIdx;
+}
+
+void FrontendOptionSetCursor(int screen, int8 option, bool overwrite)
+{
+ currentMenu = screen;
+ optionCursor = option;
+ optionOverwrite = overwrite;
+}
+
+void FrontendOptionAddBuiltinAction(const wchar* leftText, int action, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc) {
+ int8 screenOptionOrder = RegisterNewOption();
+
+ FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1];
+
+ // To fool the Frontend, we will still display the text passed via first param.
+ switch (action) {
+ case MENUACTION_SCREENRES:
+ strcpy(aScreens[currentMenu].m_aEntries[screenOptionOrder].m_EntryName, "FED_RES");
+ break;
+ case MENUACTION_AUDIOHW:
+ strcpy(aScreens[currentMenu].m_aEntries[screenOptionOrder].m_EntryName, "FEA_3DH");
+ break;
+ }
+ aScreens[currentMenu].m_aEntries[screenOptionOrder].m_Action = action;
+ option.type = FEOPTION_BUILTIN_ACTION;
+ option.buttonPressFunc = buttonPressFunc;
+ TextCopy(option.leftText, leftText);
+ option.screenOptionOrder = screenOptionOrder;
+ option.returnPrevPageFunc = returnPrevPageFunc;
+ option.save = false;
+}
+
+void FrontendOptionAddSelect(const wchar* leftText, const wchar** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, ReturnPrevPageFunc returnPrevPageFunc, bool save)
+{
+ int8 screenOptionOrder = RegisterNewOption();
+
+ FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1];
+ option.type = FEOPTION_SELECT;
+ TextCopy(option.leftText, leftText);
+ option.rightTexts = rightTexts;
+ option.numRightTexts = numRightTexts;
+ option.value = var;
+ option.displayedValue = *var;
+ option.lastSavedValue = *var;
+ option.save = save;
+ option.onlyApplyOnEnter = onlyApplyOnEnter;
+ option.changeFunc = changeFunc;
+ option.screenOptionOrder = screenOptionOrder;
+ option.returnPrevPageFunc = returnPrevPageFunc;
+}
+
+void FrontendOptionAddDynamic(const wchar* leftText, DrawFunc drawFunc, int8 *var, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc, bool save)
+{
+ int8 screenOptionOrder = RegisterNewOption();
+
+ FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1];
+ option.type = FEOPTION_DYNAMIC;
+ option.drawFunc = drawFunc;
+ option.buttonPressFunc = buttonPressFunc;
+ TextCopy(option.leftText, leftText);
+ option.value = var;
+ option.save = save;
+ option.screenOptionOrder = screenOptionOrder;
+ option.returnPrevPageFunc = returnPrevPageFunc;
+}
+
+void FrontendOptionAddRedirect(const wchar* text, int to, int8 selectedOption, bool fadeIn)
+{
+ int8 screenOptionOrder = RegisterNewOption();
+
+ FrontendOption &option = customFrontendOptions[numCustomFrontendOptions - 1];
+ option.type = FEOPTION_REDIRECT;
+ option.to = to;
+ option.option = selectedOption;
+ option.fadeIn = fadeIn;
+ TextCopy(option.leftText, text);
+ option.screenOptionOrder = screenOptionOrder;
+ option.returnPrevPageFunc = nil;
+ option.save = false;
+}
+
+void FrontendOptionAddBackButton(const wchar* text, bool fadeIn)
+{
+ int8 screenOptionOrder = RegisterNewOption();
+
+ FrontendOption& option = customFrontendOptions[numCustomFrontendOptions - 1];
+ option.type = FEOPTION_GOBACK;
+ option.fadeIn = fadeIn;
+ TextCopy(option.leftText, text);
+ option.screenOptionOrder = screenOptionOrder;
+ option.returnPrevPageFunc = nil;
+ option.save = false;
+}
+
+uint8 FrontendScreenAdd(char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight,
+ int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc) {
+
+ uint8 screenOrder = RegisterNewScreen(gxtKey, prevPage);
+
+ FrontendScreen &screen = customFrontendScreens[numCustomFrontendScreens - 1];
+ screen.id = screenOrder;
+ screen.sprite = sprite;
+ screen.prevPage = prevPage;
+ strncpy(screen.name, gxtKey, 8);
+ screen.columnWidth = columnWidth;
+ screen.headerHeight = headerHeight;
+ screen.lineHeight = lineHeight;
+ screen.font = font;
+ screen.fontScaleX = fontScaleX;
+ screen.fontScaleY = fontScaleY;
+ screen.alignment = alignment;
+ screen.returnPrevPageFunc = returnPrevPageFunc;
+
+ return screenOrder;
+}
+#endif \ No newline at end of file
diff --git a/src/extras/frontendoption.h b/src/extras/frontendoption.h
new file mode 100644
index 00000000..9608870b
--- /dev/null
+++ b/src/extras/frontendoption.h
@@ -0,0 +1,162 @@
+#pragma once
+#include "common.h"
+
+#ifdef CUSTOM_FRONTEND_OPTIONS
+#include "Frontend.h"
+
+// Warning:
+// All of the code relies on that you won't use more then NUM_MENUROWS(18) options on one page.
+// Also congrats if you can make 18 options visible at once.
+
+// About texts:
+// All text parameters accept wchar(including hardcoded wchar* and TheText.Get)
+// except FrontendScreenAdd(it's char[8] by the design of Frontend).
+// All texts reload if custom options reloaded too, which includes language changes and via live reload feature in debug menu!
+
+// Execute direction:
+// All of the calls below eventually manipulate the aScreens array, so keep in mind to add/replace options in order,
+// i.e. don't set cursor to 8 first and then 3.
+
+// Live reload:
+// You can add/change/undo the new options in-game if you use VS. Change what you want, build the changed bits via "Edit and Continue",
+// and hit the "Reload custom frontend options" from debug menu. Or call CustomFrontendOptionsPopulate() from somewhere else.
+
+
+// -- Option types
+//
+// Static/select: You allocate the variable, pass it to function and game sets it from user input among the strings given to function,
+// then you can handle ChangeFunc(only called on enter if onlyApplyOnEnter set, or set immediately)
+// and ReturnPrevPageFunc optionally. You can store the option in gta3.set if you pass true to corresponding parameter.
+//
+// Dynamic: Passing variable to function is only needed if you want to store it, otherwise you should do
+// all the operations with ButtonPressFunc, this includes allocating the variable.
+// Left-side text is passed while creating and static, but ofc right-side text is dynamic -
+// you should return it in DrawFunc, which is called on every draw. ReturnPrevPageFunc is also here if needed.
+//
+// Redirect: Redirection to another screen. selectedOption parameter is the highlighted option user will see after the redirection.
+//
+// Built-in action: As the name suggests, any action that game has built-in. But as an extra you can set the option text,
+// and can be informed on button press/focus loss via buttonPressFunc. ReturnPrevPageFunc is also here.
+
+#define FEOPTION_SELECT 0
+#define FEOPTION_DYNAMIC 1
+#define FEOPTION_REDIRECT 2
+#define FEOPTION_GOBACK 3
+#define FEOPTION_BUILTIN_ACTION 4
+
+// -- Returned via ButtonPressFunc() action param.
+#define FEOPTION_ACTION_LEFT 0
+#define FEOPTION_ACTION_RIGHT 1
+#define FEOPTION_ACTION_SELECT 2
+#define FEOPTION_ACTION_FOCUSLOSS 3
+
+// -- Passed via FrontendScreenAdd()
+#define FESCREEN_CENTER 0
+#define FESCREEN_LEFT_ALIGN 1
+#define FESCREEN_RIGHT_ALIGN 2
+
+// -- Callbacks
+
+// pretty much in everything I guess, and optional in all of them
+typedef void (*ReturnPrevPageFunc)();
+
+// for static options
+typedef void (*ChangeFunc)(int8 displayedValue); // called before updating the value.
+ // only called on enter if onlyApplyOnEnter set, otherwise called on every value change
+
+// for dynamic options
+typedef wchar* (*DrawFunc)(bool* disabled, bool userHovering); // you must return a pointer for right text.
+ // you can also set *disabled if you want to gray it out.
+typedef void (*ButtonPressFunc)(int8 action); // see FEOPTION_ACTIONs above
+
+struct FrontendScreen
+{
+ int id;
+ char name[8];
+ eMenuSprites sprite;
+ int prevPage;
+ int columnWidth;
+ int headerHeight;
+ int lineHeight;
+ int8 font;
+ float fontScaleX;
+ float fontScaleY;
+ int8 alignment;
+ bool showLeftRightHelper;
+ ReturnPrevPageFunc returnPrevPageFunc;
+};
+
+struct FrontendOption
+{
+ int8 type;
+ int8 screenOptionOrder;
+ int32 screen;
+ wchar leftText[64];
+ ReturnPrevPageFunc returnPrevPageFunc;
+ int8* value;
+ int8 displayedValue; // only if onlyApplyOnEnter enabled for now
+ bool save;
+ int32 ogOptionId; // for replacements, see overwrite parameter of SetCursor
+
+ union {
+ // Only for dynamic / built-in action
+ struct {
+ DrawFunc drawFunc;
+ ButtonPressFunc buttonPressFunc;
+ };
+
+ // Only for static/select
+ struct {
+ const wchar** rightTexts;
+ int8 numRightTexts;
+ bool onlyApplyOnEnter;
+ ChangeFunc changeFunc;
+ int8 lastSavedValue; // only if onlyApplyOnEnter enabled
+ };
+
+ // Only for redirect
+ struct {
+ int to;
+ int8 option;
+ bool fadeIn;
+ };
+ };
+};
+
+// -- Internal things
+void RemoveCustomFrontendOptions();
+void CustomFrontendOptionsPopulate();
+
+extern int lastOgScreen; // for reloading
+
+extern int numCustomFrontendOptions;
+extern FrontendOption* customFrontendOptions;
+
+extern int numCustomFrontendScreens;
+extern FrontendScreen* customFrontendScreens;
+
+// -- To be used in ButtonPressFunc / ChangeFunc(this one would be weird):
+void ChangeScreen(int screen, int option = 0, bool fadeIn = true);
+void GoBack(bool fadeIn = true);
+
+uint8 GetNumberOfMenuOptions(int screen);
+
+// -- Placing the cursor to append/overwrite option
+//
+// Done via FrontendOptionSetCursor(screen, position, overwrite = false), parameters explained below:
+// Screen: as the name suggests. Also accepts the screen IDs returned from FrontendScreenAdd.
+// Option: if positive, next AddOption call will put the option to there and progress the cursor.
+// if negative, cursor will be placed on bottom-(pos+1), so -1 means the very bottom, -2 means before the back button etc.
+// Overwrite: Use to overwrite the options, not appending a new one. AddOption calls will still progress the cursor.
+
+void FrontendOptionSetCursor(int screen, int8 option, bool overwrite = false);
+
+// var is optional in AddDynamic, you can enable save parameter if you pass one, otherwise pass nil/0
+void FrontendOptionAddBuiltinAction(const wchar* leftText, int action, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc);
+void FrontendOptionAddSelect(const wchar* leftText, const wchar** rightTexts, int8 numRightTexts, int8 *var, bool onlyApplyOnEnter, ChangeFunc changeFunc, ReturnPrevPageFunc returnPrevPageFunc, bool save = false);
+void FrontendOptionAddDynamic(const wchar* leftText, DrawFunc rightTextDrawFunc, int8 *var, ButtonPressFunc buttonPressFunc, ReturnPrevPageFunc returnPrevPageFunc, bool save = false);
+void FrontendOptionAddRedirect(const wchar* text, int to, int8 selectedOption = 0, bool fadeIn = true);
+void FrontendOptionAddBackButton(const wchar* text, bool fadeIn = true);
+
+uint8 FrontendScreenAdd(char* gxtKey, eMenuSprites sprite, int prevPage, int columnWidth, int headerHeight, int lineHeight, int8 font, float fontScaleX, float fontScaleY, int8 alignment, bool showLeftRightHelper, ReturnPrevPageFunc returnPrevPageFunc = nil);
+#endif \ No newline at end of file