Synopsis: Using VT100 escape codes for a stable prompt. September 96 From: erwin@pip.dknet.dk To: MERC/Envy Mailing List Cc: rom@cmc.net Subject: Using VT100 escape codes At last I have found out how to use the VT 100 escape codes to provide a status-bar in a MUD :) Since I do not recall anyone else sharing this with the lists, here is a rough explanation of what is necessary. Actual code is left as an exercise to the implementor; if you have no idea what to do, you shouldn't be doing this :) I have so far tested this with telnet under Linux, and tintin++ 1.6pl4. Tintin requires some slight modifications (see later) Definitions of some codes: NOTE: I am unsure of portability of \e, if it doesn't work for you, use \033 #define VT_SAVECURSOR "\e7" /* Save cursor and attrib */ #define VT_RESTORECURSOR "\e8" /* Restore cursor pos and attribs */ #define VT_SETWIN_CLEAR "\e[r" /* Clear scrollable window size */ #define VT_CLEAR_SCREEN "\e[2J" /* Clear screen */ #define VT_CLEAR_LINE "\e[2K" /* Clear this whole line */ #define VT_RESET_TERMINAL "\ec" This last code might be useful when a character quits (though it may reset just TOO much: under xterm it reset my background color from black -> white). Go to: To go to a line, send "\e[%d;%dH" where first %d = y coordinate, second one is x coordinate. Note that they are reversed. Top left of the screen is 1,1. Set window: To define a scrollable window, send "\e[%d;%dr" , where first %d is top line, and %d is bottom line. E.g. if you define a window from line 1 to 47, and you write a newline at line 47, lines 2 to 47 will be moved one line up, and line 1 will disappear. Initialization: Figure out how many lines the user wants on the screen. This could be pagelen, but often that is set to a smaller number that the actual number of lines. I use another variable, but default to pagelen. The last line is referred to hereafter as maxLine Goto maxLine,1 (that's y = maxLine, x = 1). This is where user's input will be from now on. Send the VT_CLEAR_LINE code and the VT_SAVECURSOR code to save this cursor position. Assuming a 2-line status bar, the screen should look like: line 1 .. maxLine-3 scrollable region line maxLine-2 and maxLine-1 status bar line maxLine input line Goto 1,1 now and send the SETWIN_CLEAR code (to clear any previous scrollable regions set) and then send a CLEAR_SCREEN code. Now to define the new scrollable window. Simply send the set window code. If you have 50 lines, you would define line 1 to 47 as scrollable, and would thus send the "\e[1;47r" code. Whenever something is printed in that region, the terminal will know that this is the part of screen to be scrolled. Whenever something is printed outside, no scrolling will happen. Finally go back to the input line. Processing output: Do not send the prompt if the user has VT on. In process_output() before sending anything, send first the SAVE_CURSOR command. The reason to this is that the user may be in the middle of a word while new output arrives - and we'd like him to be the same place when we have printed the output. Then print the output. After it is finished, send RESTORE_CURSOR. If the user has just typed a command, we need to clear the input field. Do that as beginning of the initialization. The fcommand field of the descriptor_data structure is set if there was any input. Status line: Somewhere before sending that RESTORE_CURSOR, you need to redraw the status bar. It's really up to you how do you want this look. I personally save each value displayed in the status bar in the descriptor_data for that player then redraw only if it has changed. It's probably a good idea to split the redrawing up in two: redraw "background" like the word "Hits:" and "Mana:" which should only be done once, and another part, which redraws the actual numbers. Sending: When sending the codes in process_output(), be sure to send the SAVECURSOR code before anything else. You could use write_to_descriptor() for that which writes to socket now, rather than write_to_buffer() which puts the stuff at the end of the buffer. I am unsure however, how 2 consecutive write calls are handled: are they split up in 2 packets, or does the OS waits a bit? Therefore, I prepend the codes to the output buffer rather than write them directly, so that everything is write'en at once. Usually, process_output() is called only when output arrives or when user has entered a command. I expanded it be called every 5 seconds (my tick size) so that the status bar is updated with new hp/mana/move values. If your system is alike, you should probably check in process_output() if something was actually written to the status bar; if not, you can save a few bytes and not just send a SAVE CURSOR, goto scrollable region, RESTORE CURSOR sequence. Also note that write_to_buffer() puts in a \n\r if there is no output in the buffer already. This is not good if you are just updating the status bar and not actually writing anything in the scrollable region. You could perhaps set a flag on the descriptor saying that it is just now updating the status bar, and leading \n\r should not be sent. Expanding: It could be possible to add another status bar at the top, for immortals for example, containg the currently editted object. You could do that easily, by setting the top of the scrollabe region to something lower, clearing the top lines, placing the cursor on the very top and sending. this will work properly with displays with 50+ lines, 25 is too few. The problem with the above is though that the top region will be outside the scrollable region and will not scroll - you'd have to remember what you have printed there. I suppose you could then temporarily define the top region as scrollable, print, then go back to your standard scrollable region. I have played with it a bit more, but am not satisified with the results. My enter_top_window() saves the current d->outtop ( called in the beginning of oedit_show for example). Then I send as usually, making sure that nothing is paged using send_to_char() but that it is written directly to the buffer. At the end of the oedit_show(), I call leave_top_window() which finds out what was written to the top window (we saved the point where we started writing stuff to the top window, so the area from d->before_entering to d->outtop is what needs to be shown up there) and compared the lines with the last written lines. The lines that have been changed since last time, are cleared and redrawn. Then the new lines are saved for the next time something is sent. Output: If you make your routine to redraw the status bar smart enough, the output should be smaller than with a normal prompt; you send only the data that needs to be updated, which could be nothing. The fact that 4 extra characters need to be sent before every output will not matter much I think. Each color code takes up more space than that. Tintin modifications: tintin needs to have #redraw on. However, the input line should not be redrawn while output from the MUD is arriving; otherwise you'll end up with lost lines in the middle of everything. What you need to do is to move the call to ctrl_r_redraw() first after the stdin file descriptor has data on it. Before: } if (maybe_redraw && redraw && !is_split) ctrl_r_redraw(); } /* end of while !FD_ISSET(0, &readfs) loop */ After: } } /* end of while !FD_ISSET(0, &readfs) loop */ if (maybe_redraw && redraw && !is_split) ctrl_r_redraw(); However, it still screws up from time to time :( Sources: To figure this out, I used the vt100.txt file found in the C snippets, plus the output of a MUD's 'vt' command :) (Shattered Kingdoms, mud.vividnet.com 1996) PS: I wouldn't mind some mail and some mention if you make this work on your MUD :) (*if* :) ============================================================================== Erwin Andreasen Viby J, Denmark Computer Science Student at Aarhus Business 4u2@aabc.dk Click here! College ============================================================================== [I have since discovered that SillyMUD offers something like this as well - you might want to look there for a working implementation]