User:Egocarib/MCM Utility Scripts

From the CreationKit Wiki
Jump to navigation Jump to search

MCM Utility Scripts[edit | edit source]

A few handy script fragments I have used in my MCM menus.


SetStarterInfoText[edit | edit source]

Sets the info text on a menu page when the user first selects it, before any options are highlighted. Call the function while you're building the page options.

Syntax[edit | edit source]

  • SetStarterInfoText(string desc)

Example[edit | edit source]

  if kPage == "Settings"
    SetTitleText("Settings Page")
    SetStarterInfoText("Choose a Setting to Adjust")
    SetCursorPosition(0)
    SetCursorFillMode(TOP_TO_BOTTOM)
    ;...

Code[edit | edit source]

  ;Description text setter, allows display of info text immediately when page is first selected:
  Function setStarterInfoText(string desc)
    registerForModEvent("_setPageStartDescrip", "OnPageDescriptionSet")
    sendModEvent("_setPageStartDescrip", desc, 0)
  EndFunction
  Event OnPageDescriptionSet(string eventName, string desc, float iterations, Form sender)
    int readyState = UI.GetInt("Journal Menu", "_global.ConfigPanel.READY")
    int curState = UI.GetInt("Journal Menu", "_root.ConfigPanelFader.configPanel._state")

    if (curState == readyState || iterations > 10.0) ;safety limiter
      UI.InvokeString("Journal Menu", "_root.ConfigPanelFader.configPanel.setInfoText", desc)
      UnregisterForModEvent("_setPageStartDescrip")
    else
      utility.waitmenumode(0.05)
      SendModEvent("_setPageStartDescrip", desc, iterations + 1.0)
    endif
  EndEvent




RefreshPage[edit | edit source]

Page resetter that maintains highlight focus on currently selected option (because forcePageReset() by itself will lose focus on the currently highlighted option). I find this useful if the user selecting an option alters other options on the page as well, and full page refresh is needed.

Syntax[edit | edit source]

  • RefreshPage()

Example[edit | edit source]

Event OnOptionSelect(int option)
  if option == myOp1
    myBool = !myBool
    RefreshPage()
    ;...

Code[edit | edit source]

  ;Page Refresh that maintains highlight on currently selected option:
  Function RefreshPage()
    int index = UI.GetInt("Journal Menu", "_root.ConfigPanelFader.configPanel.contentHolder.optionsPanel.optionsList.selectedIndex")
    registerForModEvent("_pageRefresh", "OnPageRefreshed")
    sendModEvent("_pageRefresh", index as string, 0)
    forcePageReset()
  EndFunction
  Event OnPageRefreshed(string eventName, string strArg, float iterations, Form sender)
    int returnPlace = strArg as int
    int readyState = UI.GetInt("Journal Menu", "_global.ConfigPanel.READY")
    int curState = UI.GetInt("Journal Menu", "_root.ConfigPanelFader.configPanel._state")

    if (curState == readyState || iterations > 10.0)
      UI.SetInt("Journal Menu", "_root.ConfigPanelFader.configPanel.contentHolder.optionsPanel.optionsList.selectedIndex", returnPlace)
      UnregisterForModEvent("_pageRefresh")
    else
      utility.wait(0.04)
      SendModEvent("_pageRefresh", strArg, iterations + 1.0)
    endif
  EndEvent




MCM Text Input[edit | edit source]



NOTICE: I would no longer recommend using this method, since better methods of text input have now been made easily available.


A method of allowing text input from users in the MCM menu by listening for keypresses in conjunction with a refreshing messagebox. Sounds wonky but works surprisingly well, as long as your users don't need to enter more than a few words or so (renaming items or presets, typing a string to search for, etc). I couldn't figure out how to inject my own text input swf, so I used this papyrus workaround instead.

The example currently interprets all letters as uppercase (which was good enough for my purpose), but it would be quite easy to add lower-case too by just adding the appropriate DXScanCodes and checking if the shift key is pressed as you're listening for letters. You'll notice I've also added extra spaces to all the strings to avoid papyrus string caching inconsistencies (papyrus changing the case of letters/strings unexpectedly).

In the following example, the messagebox is designed to pop up when a user selects a simple MCM option, asking them to type in their text.


Fragment 1 -- Key Arrays[edit | edit source]

Make sure to call initializeKeyCodes() before the user would use your text input option (e.g. during onConfigInit())

;key init for the input feature 
Function initializeKeyCodes()
  ;list of basic alphanumeric character keys
  DXScanCodes = new int[51]
  DXScanCodes[0] = 16   ;begin alphabet letters
  DXScanCodes[1] = 17
  DXScanCodes[2] = 18
  DXScanCodes[3] = 19
  DXScanCodes[4] = 20
  DXScanCodes[5] = 21
  DXScanCodes[6] = 22
  DXScanCodes[7] = 23
  DXScanCodes[8] = 24
  DXScanCodes[9] = 25
  DXScanCodes[10] = 30
  DXScanCodes[11] = 31
  DXScanCodes[12] = 32
  DXScanCodes[13] = 33
  DXScanCodes[14] = 34
  DXScanCodes[15] = 35
  DXScanCodes[16] = 36
  DXScanCodes[17] = 37
  DXScanCodes[18] = 38
  DXScanCodes[19] = 44
  DXScanCodes[20] = 45
  DXScanCodes[21] = 46
  DXScanCodes[22] = 47
  DXScanCodes[23] = 48
  DXScanCodes[24] = 49
  DXScanCodes[25] = 50  ;end alphabet letters
  DXScanCodes[26] = 2
  DXScanCodes[27] = 3
  DXScanCodes[28] = 4
  DXScanCodes[29] = 5
  DXScanCodes[30] = 6
  DXScanCodes[31] = 7
  DXScanCodes[32] = 8
  DXScanCodes[33] = 9
  DXScanCodes[34] = 10
  DXScanCodes[35] = 11
  DXScanCodes[36] = 12
  DXScanCodes[37] = 40
  DXScanCodes[38] = 57  ;spacebar
  DXScanCodes[39] = 71
  DXScanCodes[40] = 72
  DXScanCodes[41] = 73
  DXScanCodes[42] = 74
  DXScanCodes[43] = 75
  DXScanCodes[44] = 76
  DXScanCodes[45] = 77
  DXScanCodes[46] = 78
  DXScanCodes[47] = 79
  DXScanCodes[48] = 80
  DXScanCodes[49] = 81
  DXScanCodes[50] = 82

  ;characters correlated to Codes above:
  ;(extra space added to try and avoid papyrus string caching inconsistencies)
  DXScanChars = new string[51]
  DXScanChars[0] = "Q "
  DXScanChars[1] = "W "
  DXScanChars[2] = "E "
  DXScanChars[3] = "R "
  DXScanChars[4] = "T "
  DXScanChars[5] = "Y "
  DXScanChars[6] = "U "
  DXScanChars[7] = "I "
  DXScanChars[8] = "O "
  DXScanChars[9] = "P "
  DXScanChars[10] = "A "
  DXScanChars[11] = "S "
  DXScanChars[12] = "D "
  DXScanChars[13] = "F "
  DXScanChars[14] = "G "
  DXScanChars[15] = "H "
  DXScanChars[16] = "J "
  DXScanChars[17] = "K "
  DXScanChars[18] = "L "
  DXScanChars[19] = "Z "
  DXScanChars[20] = "X "
  DXScanChars[21] = "C "
  DXScanChars[22] = "V "
  DXScanChars[23] = "B "
  DXScanChars[24] = "N "
  DXScanChars[25] = "M "
  DXScanChars[26] = "1 "
  DXScanChars[27] = "2 "
  DXScanChars[28] = "3 "
  DXScanChars[29] = "4 "
  DXScanChars[30] = "5 "
  DXScanChars[31] = "6 "
  DXScanChars[32] = "7 "
  DXScanChars[33] = "8 "
  DXScanChars[34] = "9 "
  DXScanChars[35] = "0 "
  DXScanChars[36] = "- "
  DXScanChars[37] = "' "
  DXScanChars[38] = "  "  ;spacebar
  DXScanChars[39] = "7 "
  DXScanChars[40] = "8 "
  DXScanChars[41] = "9 "
  DXScanChars[42] = "- "
  DXScanChars[43] = "4 "
  DXScanChars[44] = "5 "
  DXScanChars[45] = "6 "
  DXScanChars[46] = "+ "
  DXScanChars[47] = "1 "
  DXScanChars[48] = "2 "
  DXScanChars[49] = "3 "
  DXScanChars[50] = "0 "
EndFunction

Fragment 2 -- The Option Handler[edit | edit source]

  ;////////////// INPUT OPTION /////////////////;

  ;...
  elseif o == presetRenameOp ;option allowing user to rename a preset
    int letterCounter
    displayString = " " ;I add a starting space to avoid any possible papyrus string caching problems (papyrus changing the case of letters)
    currentChar = ""

    ;the following safetyMechanism is required to prevent crashes caused by fast typing. Without it, Users can type
    ;in between separate iterations of the menu pop up, navigating away from the page and out of the MCM itself, at
    ;which point trying to call ShowMessage crashes the game. This safetyMechanism prevents that from ever
    ;happening by terminating text input and closing all messages once focus on this option is lost.
    string safetyPlace = "_root.ConfigPanelFader.configPanel.contentHolder.optionsPanel.optionsList.selectedIndex"
    int safetyMechanism = UI.GetInt("Journal Menu", safetyPlace)

    GoToState("RegisterInputState") ;start tracking keypresses
    typingMessage = true
    while typingMessage && (safetyMechanism == UI.GetInt("Journal Menu", safetyPlace))
      typingMessage = false
      if currentChar == ""
        ShowMessage("Begin Typing a New Preset Name.\n\n(Please avoid typing very fast)", false, "Cancel")
      elseif currentChar == "DEL"
        if letterCounter > 0
          displayString = stringUtil.subString(displayString, 0, letterCounter)
          letterCounter -= 1
          if !letterCounter
            currentChar = ""
          endif
        endif
        ShowMessage(displayString, true, "Confirm New Name", "Cancel")
      elseif currentChar != "ENT" ;enter will close the menu
        int curLength = stringUtil.getLength(displayString)
        if curLength < 41 ;max character limit (this isn't required, and could be removed)
          letterCounter += 1
          string sTemp = stringUtil.subString(displayString, 0, letterCounter)
          displayString = sTemp + currentChar
          ShowMessage(displayString, true, "Confirm New Name", "Cancel")
        else 
          ShowMessage(displayString + "\n\nCharacter Limit Reached", true, "Confirm New Name", "Cancel")
        endif
      endif
    endWhile
    GoToState("")
    if (safetyMechanism == UI.GetInt("Journal Menu", safetyPlace)) && currentChar == "ENT"
      if letterCounter
        displayString = stringUtil.subString(displayString, 1, letterCounter)
        ;;
        ;; displayString now contains the text your user entered!
        ;;
      endif
    endif

  ;...

Fragment 3 -- The Input State[edit | edit source]

;////////////// INPUT STATE /////////////////;

int[] DXScanCodes
string[] DXScanChars
string displayString ;used by menu setter
bool typingMessage
bool bTypeSpeedLimiter
string currentChar ;new tester
;bool bInputPage ;used by menu setter

State RegisterInputState
  Event OnBeginState()
    int i = 51
    while i
      i -= 1
      RegisterForKey(DXScanCodes[i])
    endWhile
    RegisterForKey(14)  ;backspace
    RegisterForKey(211) ;delete
    RegisterForKey(28)  ;enter
  EndEvent

  Event OnKeyDown(int code)
    if !bTypeSpeedLimiter
      bTypeSpeedLimiter = true
      if code == 14 || code == 211 ;backspace/delete
        currentChar = "DEL"
      elseif code == 28 ;enter
        currentChar = "ENT"
      else
        int index = DXScanCodes.find(code)
        if index >= 0
          currentChar = DXScanChars[index]
        endif
      endif
      typingMessage = true
      UnregisterForKey(28) ;only listen for user to hit enter
      Input.TapKey(28)
      RegisterForKey(28)
      utility.waitmenumode(0.3)
      ;typingMessage = false
      bTypeSpeedLimiter = false
    endif
  EndEvent

  Event OnEndState()
    UnregisterForAllKeys()
  EndEvent
EndState