Text Input With OBSE

From the Oblivion ConstructionSet Wiki
Jump to navigation Jump to search

OBSE supports a simple text input function since the version 0016, allowing the player to enter and edit text in-game with limited HTML support. This article contains the controls used by the text input function, the OBSE functions related to text input, a generic example of code used for text input, and a specific example for changing the name of an object using this text input function. The first three sections describes the text input function as it can be found in the OBSE v0021 Command documentation.


Controls[edit | edit source]

Controls while entering text:


- Left/Right arrows: move cursor left or right. Pressing in conjunction with the CTRL key moves the cursor to the beginning of the closest word.

- Home/End: move cursor to the beginning or end of the text.

- Backspace/Delete: delete text. Use with CTRL to delete an entire word.

- TAB: Inserts 4 spaces.

- Up/Down: When editing books or scrolls, move cursor to the end of the previous line or the beginning of the next line.


Additional formatting can be inserted when editing books and scrolls by pressing CTRL in combination with one of the following:


- L, R, or C: align the current line left, right, or center respectively. Alignment affects the current line and any subsequent lines until the next alignment tag.

- 1, 2, 3, 4, 5: change the font of the text.


<IMG> tags are not directly supported, but can be added using InsertInInputText. The <, >, and ~ characters are not considered valid input characters.


Note that it is possible for the user to close the menu by clicking one of the buttons in a MessageBox or the "Exit" button in a book. Calls to UpdateTextInput will re-display a messagebox with the user's text intact, but will not re-open a book once closed. Further, it is still necessary to call CloseTextInput to release the text input menu for use by other scripts.

Functions[edit | edit source]

OpenTextInput - opens a text input menu if one is not currently in use. MenuType is 0 for a MessageBox, 1 for a book, or 2 for a scroll. The MaxLength parameter specifies how many characters to allow the user to enter. For messageboxes, the prompt string will be displayed before the cursor and cannot be erased. It may also contain buttons, with the button text separated from the prompt text with pipe '|' characters as in MessageBoxEX. For books and scrolls, the prompt string serves as the default text visible as soon as the menu is opened and may be edited by the user.

(nothing) OpenTextInput promptString:string var1 var2 ... var20 menuType:int maxLength:int


IsTextInputInUse - returns 1 if a script is currently using a text input menu, regardless of whether or not the menu is visible. Only one script may request text input at a time.

(isInUse:bool) IsTextInputInUse


GetInputText - returns the text entered by the user. You may call this function at any point before calling CloseTextInput. Note that the returned string includes any html formatting inserted by the user. It also includes an html prefix along the lines of < FONT face="#" >< div align="align" >.

(inputText:string_var) GetInputText


UpdateTextInput - tells the text input menu to check for user input and refresh the displayed text if necessary. In general, unless you want to temporarily disallow input, this command should be called every frame while the text input menu is open.

(nothing) UpdateTextInput


CloseTextInput - closes the text input menu, releasing it for use by other scripts. Be sure to call this command when you are done getting input.

(nothing) CloseTextInput


InsertInInputText - inserts formatted text at the current cursor position in the text input menu, as long as doing so would not increase the length of the text beyond its maximum length.

(nothing) InsertInInputText formatString:string var1 var2 ... var20


GetTextInputControlPressed - allows scripters to define custom controls for text input. Returns the scan code of the last key pressed in conjunction with the CTRL key, excluding those keys reserved for use by the text input menu. Once the code has been retrieved, subsequent calls to this command will return -1; it will also return -1 if no control has been pressed (similar to GetButtonPressed).

(scanCode:int) GetTextInputControlPressed


DeleteFromInputText - deletes a number of characters or words from the input text in the direction specified, beginning from the current cursor position. Note that an html tag is treated as both a word and a character (it is not possible to delete only part of a tag). Both optional parameters are false by default.

(nothing) DeleteFromInputText numToDelete:int bBackwards:bool bDeleteWholeWords:bool


GetTextInputCursorPos - returns the current position of the cursor as an index into the input string

(cursorPos:int) GetTextInputCursorPos


MoveTextInputCursor - moves the cursor a specified number of characters in the specified direction from its current position. Note that each html tag is treated as a single character.

(nothing) MoveTextInputCursor numChars:int moveBackwards:bool


SetInputText - replaces any text which has been input by the user with the specified text and repositions the cursor to the specified position. If the specified position is invalid (e.g. greater than the length of the text, or inside an html tag), returns false without modifying the input text; otherwise returns true. For books, the text must be in an appropriate format - no invalid html tags should be present and the text must be prefixed with html of the format

(textSet:bool) SetInputText text:string newCursorPos:int


SetTextInputHandler - registers a function script to handle control-key combos pressed while the current text input menu is active. The function script should accept a single integer argument; when a key is pressed in conjunction with the Control key, that key's scan code will be passed to the function script. The script will not be informed of control-key combos which are handled by OBSE (for instance, ctrl+5 to change to font #5) unless the default controls have been disabled with SetTextInputDefaultControlsDisabled. The function script is unregistered when the text input menu is closed.

(nothing) SetTextInputHandler functionScript:ref


SetTextInputDefaultControlsDisabled - sets whether or not OBSE responds to control-key combos it recognizes, such as ctrl+(number) for changing the font. While default controls are disabled, they will be passed to the function script registered with SetTextInputHandler or stored for retrieval using GetTextInputControlPressed. This setting is reset when the text input menu is closed.

(nothing) SetTextInputDefaultControlsDisabled disableDefaultControls:bool


Generic code example[edit | edit source]

scriptName GetUserInputSCR
int button
int state
string_var userText

begin OnEquip
	if (IsTextInputInUse == 0)
		OpenTextInput "Type stuff (max 20 chars) | Finished" 0 20
		set state to 1
	endif
end

begin menuMode
	if (state)
		UpdateTextInput
	endif
end

begin gameMode
  if (state)
	set button to GetButtonPressed
	if (button == 0) ; user has finished
	  set userText to GetInputText
	  CloseTextInput
	  set State to 0
	endif
  endif
end

Specific code example : How to allow object name changing in-game[edit | edit source]

I added this section as a bonus, first to have the opportunity to detail each step of the generic code example given by the OBSE documentation, second because I never found any tutorial explaining how to change the name of an object or cell, and because such a tutorial is necessary because of some subtleties of the functions involved.

The code[edit | edit source]

First here is the entire code, based on the above. It is a personal code, maybe not the best, but it is fully functional.

ScriptName ChangeUserObjectNameScript

int State
string_var NewName
string_var FormerName

Begin OnActivate
    If IsTextInputInUse == 0
	Let FormerName := UserObjectRef.GetName
	OpenTextInput "How would you name your UserObject ? | Done" 0
	InsertInInputText "%z", FormerName
	Set State to 1
   EndIf
End

Begin MenuMode
   If ( State )
	UpdateTextInput
   EndIf
End

Begin GameMode
   If State == 1
	Let NewName := GetInputText
	UserObjectRef.SetNameEx "%z" NewName
	CloseTextInput
	Set State to 0
   EndIf
End

Inside the code[edit | edit source]

Now, some detailed explanations of what the code do.

First, you have to choose what will trigger the text input. In this particular case, I used an Activator, that is an object placed in the world. When the player activate it, it would open the text input where the player will be able to choose the name he wants his target object to have from now on. That mean you also have to choose a target object (called UserObject in this script) which name will be changed. It can be anything, including the Activator itself, but for this script it correspond to a reference placed in the world which has to be persistent and have a Reference Editor ID (which is called in the script UserObjectRef). See under for the difference in what should be done to change the name of an item (it mean an object which can go into the player inventory) or of a cell.


int State
string_var NewName
string_var FormerName

This script only need three variable declared : the integrer variable "State" (which is used to inform the script of the state of the text input and will serve as a Do Once), and two string variables, "NewName" and "FormerName" which will store the names of the UserObject.



Begin OnActivate
    If IsTextInputInUse == 0
	Let FormerName := UserObjectRef.GetName
	OpenTextInput "How would you name your UserObject ? | Done" 0
	InsertInInputText "%z", FormerName
	Set State to 1
   EndIf
End

This block is the trigger block of the script. It is here an OnActivate block because it triggers when an Activator placer in the world in activated by the player, but it could be an OnEquip if the text input is to be called by equipping an object.


If IsTextInputInUse == 0

This part is used to be sure there is no other text input already opened. If it was the case, the text input function would not work anyway, for there can only be one text input opened at the same time.


Let FormerName := UserObjectRef.GetName

This part is not mandatory. It assigns to the "FormerName" string variable the name of the UserObject before its modification.


OpenTextInput "How would you name your UserObject ? | Done" 0

This is the command opening the menu interface for the player to enter his text. It displays a MessageBox because the MenuType specified is "0". This MessageBox contains a message the player cannot edit (How would you name your UserObject ?) and has only one button the player can click on, Done. The place where the player will enter his text is automatically displayed after the message. Note that I haven't included a text length limitation here.


InsertInInputText "%z", FormerName

This part is not mandatory. It insert as editable text the current unmodified name of the UserObject. The text displayed is between the " ", the %z is an OBSE expression which call the content of the string variable specified after the " ". Here, the content of "FormerName" has been set to the current name of the UserObject, so it will be displayed in the text field. If the player want to set his own name, he would have to delete the previous name. I use this in case the player doesn't want to change the name after all, for example if he have accidently activated the script while he didn't itend to change the name, for then he has not to enter the former name again.


set State to 1

This is the information of the state of the text input. State will be let to 1 until the player has entered a convienient name.



Begin MenuMode
   If ( State )
	UpdateTextInput
   EndIf
End

Same block as in the generic example. It runs as long as the MessageBox for text input is opened (MenuMode). ( State ) is the equivalent of State == 1. At every frame, the MessageBox will update in order to display any modification the player did to the text currently edited.



Begin GameMode
   If State == 1
	Let NewName := GetInputText
	UserObjectRef.SetNameEx "%z" NewName
	CloseTextInput
	Set State to 0
   EndIf
End

This runs after the player hit the "Done" button of the MessageBox. Hitting the Done button automatically close the MessageBox (without need to call it in the script), sending back the player into GameMode. But remember that hitting the done button doesn't automatically close the text input function, so you still have to call it in the script.


If State == 1

This will act as a Do Once function, in order to apply the changes only once. After all the changes are applied, it so call the line "Set State to 0"


Let NewName := GetInputText

This line stores the text the player has just entered before hitting "Done" into the string variable "NewName". From now on, "NewName" can be called to modify the name of any object.


UserObjectRef.SetNameEx "%z" NewName

This is the function modifying the name of the UserObject. It is mandatory to use the SetNameEx function and not the SetName, for SetName won't accept the %z as referring to a further string variable. It is also not possible to use the ModName function ; indeed, this function awaits the name of an item as its final parameter and then doesn't allow the use of the %z expression.


CloseTextInput

Do not forget this line ! Even if the MessageBox has been closed by hitting the "Done" button, the text input function is not, and it is mandatory to call this function to close it, or it won't be possible to use it anymore until it is called.


And that's almost it. Of course it is possible to change the name of multiple objects at the same time (for example, an activator and a map marker). Simply add new SetNameEx lines between "Let NewName := GetInputText" and "CloseTextInput".

Prevent a bug[edit | edit source]

The script described above cannot be used by its own only. Indeed, it use the SetNameEx function which has an inconvenient : the modification it does is not stored in the savegame, but in the memory (see here). It means two things : once the new name applied, it will apply to every instance of the UserObject in every save that is loaded within the same game session, even if the save refers to a state before the name modification by the player. Also, after exiting the game, the modification is lost, so after reloading a save in which the name was changed, the original name of the object will be displayed instead of the new one.

Here is the solution I use to prevent this from happening. It involves having a separate script to run in GameMode, but I doesn't know any other way to do it.


What you need is a script which will check each time a savegame is loaded, if the name of the UserObject has already been changed, and reapply this change to the UserObject. This script has to run at each loading of a new save, so the best to do so is to place it in a Quest script which will run at start game and will never be stopped (i.e., in a quest that will never be ended by the StopQuest function), for example a quest controlling generic dialog of NPCs. It is better to place it at the beginning of the blocks of the Quest script, in order to be sure its execution won't be blocked by a Return function used by an upper block.


The code I propose is this one :

Begin Gamemode
   If GetGameLoaded == 1
	If UserObjectRef.NewName != 0
		UserObjectRef.SetNameEx "%z" UserObjectRef.NewName
	ElseIf UserObjectRef.CompareName "Original Name" != 1
		UserObjectRef.SetName "Original Name"
	EndIf
   Endif
End

There is no variable declared by this script, because it uses the variables already declared in your "ChangeUserObjectNameScript". To call a variable from another script, you have to use the format "UserObjectRef.NewName", where "UserObjectRef" is the Reference Editor ID of the object the script is attached to and not the name of the script, and "NewName" the variable as it is declared in the script. I use the Reference Editor ID because I attached my "ChangeUserObjectNameScript" to the Activator I used to trigger the script. If the "ChangeUserObjectNameScript" is attached to a quest, you have to use the Quest ID instead of the Reference Editor ID.


So here it what this piece of code do :

If GetGameLoaded == 1

GetGameLoaded is a function returning 1 if a save has been loaded since last time it was called. So it will be true only once at each loading of a savegame.


If UserObjectRef.NewName != 0
   UserObjectRef.SetNameEx "%z" UserObjectRef.NewName

This condition checks if the name of the UserObject has already been changed in this savegame. Indeed, the SetNameEx is not store in the savegame, but the "NewName" string variable is. So if the player has once changed the name of his UserObject, this name is still stored in the savegame in "NewName". Then the second line calls this "NewName" string to replace the name of the UserObject by using the SetNameEx function again. If the player has never changed the name of his UserObject, then the "NewName" variable is still at 0, and the condition is not true.


ElseIf UserObjectRef.CompareName "Original Name" != 1
   UserObjectRef.SetName "Original Name"

This is used to compensate the first part of the bug, the one in which loading a save where the name of UserObject has not been changed by the player will result in the name of UserObject will still be modified if the player has changed it previously during the same game session. If the user has not previously changed the name of the UserObject (UserObjectRef.NewName == 0), the name of the UserObject should be its Original Name. Hence, if it is not (i.e. if it has been replaced by the "NewName" previously defined in the same game session), this code replace it by the Original Name. Original Name is not an expression recognized by the engine, you have to replace it in the script between the " " by the name the UserObject has when it has never been modified.


And that should do it.

Change the name of an item[edit | edit source]

To change the name of an item, use the SetNameEx this way :

SetNameEx "%z" NewName UserObject

where "UserObject" is the Editor ID, instead of

UserObjectRef.SetNameEx "%z" NewName


Note that this will change the name of all instances of the UserObject. The same function has to be called at each loading of the save by the code described above.

Change the name of a cell[edit | edit source]

The function to change the name of a cell is different from the one used to change the name of an object. The block has to be slightly changed to use it :

Begin _GameMode
	If State == 1
		Let NewName := GetInputText
		SetCellFullName UserCell "%z" NewName
		CloseTextInput
		Set State to 0
	EndIf
End

This is the same Begin GameMode block that the one used by the script I gave the first time, with two differences :

First, the function used to modify the name of a cell is SetCellFullName, which has to be follow by the ID of the cell you want to rename (UseCell here), then by the new name between " ". Here %z still calls the "NewName" content, which has previously been set by the player using the ext editor.

Second, and not the least for without it the code won't work, you may have notice that "Begin GameMode" has changed to "Begin _GameMode", with a "_" right before the block type. This is called a Compiler override (at least in NVSE. You can find some information on it in the OBSE documentation, in "OBSE expression" under the section "Using OBSE expressions in scripts"). This is mandatory, because normally, SetCellFullName doesn't accept any parameter after its " " section, so it is impossible to place a %z in it to refer to a string variable after it. The compiler override allows the vanilla CS functions to accept OBSE expression. It is a recent function of OBSE, set in place by the v0020.

The advantage of the SetCellFullName is the modification it does is stored in the savegame, so you don't have to compensate for the loss of the new name after the game is exited as described in the section "Prevent a bug". So you don't need to have an extra piece of script to run at each loading of a save.


See Also[edit | edit source]