Difference between revisions of "Text Input With OBSE"

9,284 bytes added ,  11:02, 20 May 2019
m
no edit summary
imported>Speedo
(Added see also section)
imported>Tomius37
m
 
(10 intermediate revisions by 3 users not shown)
Line 1: Line 1:
This code will accept keystrokes from the player, and display the text they write in a MessageBox. It will properly account for the Shift key and the Backspace key, and it will do so without causing any bloating whatsoever.
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.


== Setup ==


First of all, this script ''requires'' the [[:Category: Oblivion Script Extender|Oblivion Script Extender]], v0014 or higher.
== Controls ==


The script stores the string in the name of an object - you need an object defined for this purpose. This object may be anything that has a name, though it is highly recommended that the player never see it, or if they do, make sure it is ''after'' they have named it. An activator is most likely the best choice.
Controls while entering text:


The string must have an initial value that ends in an accent mark (`). I used "`Enter Name`".


To use any other name, the script must be changed in five places - and these five places must have the same value. These five places have been marked with comments stating "'''!!!INIT NAME!!!''''".
- 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.


Additionally, the name must have a trailing tilde (~) in two of these places. They have been marked "'''w/ ~'''" and the other three have been marked "'''w/o ~'''".
- Home/End: move cursor to the beginning or end of the text.


Finally, the name cannot be "'''`~~`'''", unless you change the places where this string is used in the script. It is used in six places, in two sets of three.
- Backspace/Delete: delete text. Use with CTRL to delete an entire word.


Further, you also need a separate Activator to actually put the script on. This activator may be placed somewhere for the player to activate it, or else it must be placed somewhere, the Reference marked Persistent, and given a Reference ID so that the script can be activated from another script.
- TAB: Inserts 4 spaces.


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


As discussed above, the initial name of the object, as defined by the script, has some special rules. See the [[MessageBoxEx-based Text Input Box#Setup|Setup section]] for more information.


The script as-is only supports Roman letters, spaces, dashes, and apostrophes. Support for additional characters is relatively simple, but keep in mind that you will have to do this yourself if you desire it.
Additional formatting can be inserted when editing books and scrolls by pressing CTRL in combination with one of the following:


Further, Caps Lock is ignored. Given that it is generally used for running, this is probably preferable, but supporting Caps Lock could be added to the script with little effort.


Finally, it is impossible to support the accent mark (`), tilde (~), or double-quotation mark ("). The first two cannot be supported because it is impossible for the player to actually hit that key without opening the Console - even if it is [[DisableKey|disabled]]. The last cannot be supported simply because it is impossible to include one within a string in a script, as it ends the string (and Oblivion does not include any escape character for it).
- 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.


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


The following functions are used in this code:
* [[GameMode]]
* [[OnActivate]]
* [[Activate]]
* [[SetName]]
* [[ModName]]
* [[AppendToName]]
* [[OnKeyDown]]
* [[IsKeyPressed3]]
* [[MessageBoxEX]]
* [[Message]]
* [[GetButtonPressed]]
It is recommended that you have some idea of what these functions do and how they work.


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


<pre>scn TextInputBox
; Object script, goes on an Activator


short state
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.
short caps
short button


ref StringVar
== Functions ==


Begin GameMode
'''[[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 [[book|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.
<pre>(nothing) OpenTextInput promptString:string var1 var2 ... var20 menuType:int maxLength:int</pre>
 
 
'''[[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.
<pre>(isInUse:bool) IsTextInputInUse</pre>
 
 
'''[[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" >.
<pre>(inputText:string_var) GetInputText</pre>
 
 
'''[[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.
<pre>(nothing) UpdateTextInput</pre>
 
 
'''[[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.
<pre>(nothing) CloseTextInput</pre>
 
 
'''[[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.
<pre>(nothing) InsertInInputText formatString:string var1 var2 ... var20</pre>
 
 
'''[[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]]).
<pre>(scanCode:int) GetTextInputControlPressed</pre>
 
 
'''[[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.
<pre>(nothing) DeleteFromInputText numToDelete:int bBackwards:bool bDeleteWholeWords:bool</pre>
 
 
'''[[GetTextInputCursorPos]]''' - returns the current position of the cursor as an index into the input string
<pre>(cursorPos:int) GetTextInputCursorPos</pre>
 
 
'''[[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.
<pre>(nothing) MoveTextInputCursor numChars:int moveBackwards:bool</pre>
 
 
'''[[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 <FONT face="#"><div align="align">
<pre>(textSet:bool) SetInputText text:string newCursorPos:int</pre>
 
 
'''[[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.
<pre>(nothing) SetTextInputHandler functionScript:ref</pre>
 
 
'''[[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.
<pre>(nothing) SetTextInputDefaultControlsDisabled disableDefaultControls:bool</pre>
 
 
== Generic code example ==
 
<pre>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


   if ( state )
begin gameMode
    Activate player 1
   if (state)
set button to GetButtonPressed
if (button == 0) ; user has finished
  set userText to GetInputText
  CloseTextInput
  set State to 0
endif
   endif
   endif
end</pre>


== Specific code example : How to allow object name changing in-game  ==
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 ===
First here is the entire code, based on the above. It is a personal code, maybe not the best, but it is fully functional.
<pre>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
End


Begin MenuMode
Begin MenuMode
  If ( State )
UpdateTextInput
  EndIf
End


  if ( state )
Begin GameMode
    Activate player 1
  If State == 1
  endif
Let NewName := GetInputText
UserObjectRef.SetNameEx "%z" NewName
CloseTextInput
Set State to 0
  EndIf
End</pre>
 
=== Inside the code ===
 
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.
 
 
<pre>int State
string_var NewName
string_var FormerName</pre>
 
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.
 
 
 
 
<pre>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</pre>
 
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.
 
 
<pre>If IsTextInputInUse == 0</pre>
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.
 
 
<pre>Let FormerName := UserObjectRef.GetName</pre>
This part is not mandatory. It assigns to the "FormerName" string variable the name of the UserObject before its modification.
 
 
<pre>OpenTextInput "How would you name your UserObject ? | Done" 0</pre>
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.
 
 
<pre>InsertInInputText "%z", FormerName</pre>
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.
 
 
<pre>set State to 1</pre>
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.
 
 
 
 
<pre>Begin MenuMode
  If ( State )
UpdateTextInput
  EndIf
End</pre>
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.
 
 
 
 
<pre>Begin GameMode
  If State == 1
Let NewName := GetInputText
UserObjectRef.SetNameEx "%z" NewName
CloseTextInput
Set State to 0
  EndIf
End</pre>
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.
 
 
<pre>If State == 1</pre>
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"
 
 
<pre>Let NewName := GetInputText</pre>
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.
 
 
<pre>UserObjectRef.SetNameEx "%z" NewName</pre>
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.
 
 
<pre>CloseTextInput</pre>
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 ===
 
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 [[SetName|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 :
 
<pre>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</pre>
 
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 :
 
<pre>If GetGameLoaded == 1</pre>
[[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.


End


Begin OnActivate
<pre>If UserObjectRef.NewName != 0
  UserObjectRef.SetNameEx "%z" UserObjectRef.NewName</pre>
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.


  if ( state == 0 )
    OnKeyDown 57  ; clears the counter from the initial press when activating the item
    GetButtonPressed  ; clears the counter as well
    if ( StringVar == 0 )
      set StringVar to INSERT_NAME_STORAGE_OBJECT_ID_HERE  ; this is where your string gets stored
    endif
    SetName "`Enter Name`~" StringVar  ; !!!INIT NAME!!! w/ ~
    set state to 1


  elseif ( state == 1 )
<pre>ElseIf UserObjectRef.CompareName "Original Name" != 1
    ModName "~|" StringVar
  UserObjectRef.SetName "Original Name"</pre>
    MessageBoxEx "%n|Done" StringVar  ; the specific text can be changed to meet your needs
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
    ModName "`Enter Name`|" StringVar  ; !!!INIT NAME!!! w/o ~
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.
    AppendToName "~" StringVar
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.'''
    set state to 2


  elseif ( state == 2 )
    ; first, standard MessageBox menu stuffs
    set button to GetButtonPressed + 1
    if ( button )  ; pressed Done
      set state to 3
      return
    endif


    ; now String-capturing code
And that should do it.
    set caps to 0
    if ( IsKeyPressed3 42 ) || ( IsKeyPressed3 54 )  ; either Shift key
      set caps to 1
    endif


    if ( OnKeyDown 16 )
=== Change the name of an item ===
      if ( caps )
        ModName "~|Q~" StringVar
      else
        ModName "~|q~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 17 )
      if ( caps )
        ModName "~|W~" StringVar
      else
        ModName "~|w~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 18 )
      if ( caps )
        ModName "~|E~" StringVar
      else
        ModName "~|e~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 19 )
      if ( caps )
        ModName "~|R~" StringVar
      else
        ModName "~|r~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 20 )
      if ( caps )
        ModName "~|T~" StringVar
      else
        ModName "~|t~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 21 )
      if ( caps )
        ModName "~|Y~" StringVar
      else
        ModName "~|y~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 22 )
      if ( caps )
        ModName "~|U~" StringVar
      else
        ModName "~|u~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 23 )
      if ( caps )
        ModName "~|I~" StringVar
      else
        ModName "~|i~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 24 )
      if ( caps )
        ModName "~|O~" StringVar
      else
        ModName "~|o~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 25 )
      if ( caps )
        ModName "~|P~" StringVar
      else
        ModName "~|p~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 30 )
      if ( caps )
        ModName "~|A~" StringVar
      else
        ModName "~|a~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 31 )
      if ( caps )
        ModName "~|S~" StringVar
      else
        ModName "~|s~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 32 )
      if ( caps )
        ModName "~|D~" StringVar
      else
        ModName "~|d~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 33 )
      if ( caps )
        ModName "~|F~" StringVar
      else
        ModName "~|f~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 34 )
      if ( caps )
        ModName "~|G~" StringVar
      else
        ModName "~|g~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 35 )
      if ( caps )
        ModName "~|H~" StringVar
      else
        ModName "~|h~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 36 )
      if ( caps )
        ModName "~|J~" StringVar
      else
        ModName "~|j~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 37 )
      if ( caps )
        ModName "~|K~" StringVar
      else
        ModName "~|k~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 38 )
      if ( caps )
        ModName "~|L~" StringVar
      else
        ModName "~|l~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 44 )
      if ( caps )
        ModName "~|Z~" StringVar
      else
        ModName "~|z~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 45 )
      if ( caps )
        ModName "~|X~" StringVar
      else
        ModName "~|x~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 46 )
      if ( caps )
        ModName "~|C~" StringVar
      else
        ModName "~|c~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 47 )
      if ( caps )
        ModName "~|V~" StringVar
      else
        ModName "~|v~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 48 )
      if ( caps )
        ModName "~|B~" StringVar
      else
        ModName "~|b~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 49 )
      if ( caps )
        ModName "~|N~" StringVar
      else
        ModName "~|n~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 50 )
      if ( caps )
        ModName "~|M~" StringVar
      else
        ModName "~|m~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 12 )
      if ( caps )
        ModName "~|_~" StringVar
      else
        ModName "~|-~" StringVar
      endif
      set state to 1
    elseif ( OnKeyDown 57 )
      ModName "~| ~" StringVar
      set state to 1
    elseif ( OnKeyDown 40 )  ; CANNOT type double-quote mark
      ModName "~|'~" StringVar
      set state to 1


    elseif ( OnKeyDown 14 ) ; Backspace
To change the name of an item, use the [[SetNameEx]] this way :
      Label 0


      ModName "q~|" StringVar
<pre>SetNameEx "%z" NewName UserObject</pre>
      ModName "w~|" StringVar
      ModName "e~|" StringVar
      ModName "r~|" StringVar
      ModName "t~|" StringVar
      ModName "y~|" StringVar
      ModName "u~|" StringVar
      ModName "i~|" StringVar
      ModName "o~|" StringVar
      ModName "p~|" StringVar
      ModName "a~|" StringVar
      ModName "s~|" StringVar
      ModName "d~|" StringVar
      ModName "f~|" StringVar
      ModName "g~|" StringVar
      ModName "h~|" StringVar
      ModName "j~|" StringVar
      ModName "k~|" StringVar
      ModName "l~|" StringVar
      ModName "z~|" StringVar
      ModName "x~|" StringVar
      ModName "c~|" StringVar
      ModName "v~|" StringVar
      ModName "b~|" StringVar
      ModName "n~|" StringVar
      ModName "m~|" StringVar
      ModName "-~|" StringVar
      ModName "_~|" StringVar
      ModName " ~|" StringVar
      ModName "'~|" StringVar
      ModName " ~|" StringVar
      AppendToName "~" StringVar
      ModName "~~|~" StringVar


      ModName "|`~~`" StringVar
where "UserObject" is the Editor ID, instead of
      ModName "`~~`~|`Enter Name`~" StringVar  ; !!!INIT NAME!!! w/ ~
      ModName "`~~`|" StringVar


      if ( IsKeyPressed3 29 ) || ( IsKeyPressed3 157 ) ; Ctrl+Delete deletes an entire word.
<pre>UserObjectRef.SetNameEx "%z" NewName</pre>
        if ( CompareName " ~" StringVar != 1 )
          if ( CompareName "`Enter Name`" StringVar != 1 )  ; !!!INIT NAME!!! w/o ~
            Goto 0
          endif
        endif
      endif


      set state to 1


    elseif ( OnKeyDown 28 )  ; Enter - ends menu same as Done
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.
      set state to 3


    endif
=== Change the name of a cell ===


  elseif ( state == 3 )
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 :
    ModName "|`~~`" StringVar
    if ( CompareName "`~~`~" StringVar )
      SetName "`Enter Name`" StringVar  ; !!!INIT NAME!!! w/o ~
      Message "Must enter a new name."
      set state to 1
    else
      ModName "`~~`|" StringVar
      ModName "~|" StringVar
      set state to 0
      set button to 0
      ; include any code to use the string here
      MessageBoxEx "Congratulations, you named it %n" StringVar
    endif
  endif


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


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


The text box will appear whenever the object that the script is attached to is Activated. So either place the Activator somewhere that the player will see and activate it, or use this code:
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.
ActivatorRef.Activate player 1


Further, the final block of code should be modified for your needs. It is important to remove the trailing tilde and reset the two short variables, but anything else is possible or optional.
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.


== Final Comments ==
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.


This code has been thoroughly tested, and should work as long as you point StringVar to an acceptable object. Please report any bugs to the Talk page. If you believe you know what changes to the code are necessary to fix the bug, please make these changes, but also please report what was changed and why in the Talk page. Thank you.


==See Also==
==See Also==
Anonymous user