TASVideos

Tool-assisted game movies
When human skills are just not enough

Lua Scripting / Display

<< Lua Scripting

This page describes how to display text, as well as display memory addresses in the form of text.

Displaying something

Use the command gui.text(int x, int y, string msg[, type color]), which displays msg at (x,y) with color color. Color, which is optional, is given as string "0xrrggbbaa" (hex) or as a string name (e.g. "red").

Example, a very simple program:

 local str="test"
 while true do
  gui.text(0,0, str)
  emu.frameadvance()
 end
"test" can also be written directly in gui.text as gui.text(0,0, "test").

There are other drawing functions such as gui.pixel(int x, int y[, type color]) and gui.line(int x1, int y1, int x2, int y2[, type color]) but gui.text is the most informative.

Since gui functions only affect the next frame, these commands have to be called every frame to keep showing them.

Depending on the emulator, there may be other commands such as emu.message(string msg) which display text. The difference is that gui.text is a drawing function whereas emulator message functions display messages that are not considered to be drawn on the game display.

Memory Observation and Calculation.

Most emulators have a RAM Watch or similar tool to view any desired memory address while making the TAS. However, it is sometimes desirable to draw information on the screen. Drawing on the screen does not affect the playback of the movie.

First, memory search is required to find the addresses.

Once you have the addresses, it is a simple matter to display what it contains. For example, let's suppose the HP (hit points) of a boss is contained in the address 0x100 (0x100 means hexadecimal 100). The following script displays the address:

 local HPAddr=0x100
 local HP

 while true do
  HP=memory.readword(HPAddr)
  gui.text(0,0,"HP: " .. HP)
  emu.frameadvance()
 end
The function readbyte is for one-byte values, readword for two bytes, and readdword for four bytes.

You can even do calculations before outputting. For example, in Super Mario Bros., 0x0086 is Mario's X pixel position, and 0x400 is Mario's X subpixel position. Thus, we can calculate position as (addr(0x0086) + addr(0x400)/256).

 local Pixel = 0x0086
 local Subpixel = 0x0400
 local Position%%%

 while true do
  gui.text(20,20,"X Pixel" .. memory.readbyte(Pixel) )
  gui.text(22,30,"X Subpixel" .. memory.readbyte(Subpixel) )
  Position = memory.readbyte(Pixel) + (memory.readbyte(Subpixel)/256)
  gui.text(24,40,"X Position" .. Position)
  emu.frameadvance()
 end
Text is not the only option. It is possible to mark down positions of enemies on the screen. For example, suppose there is an invisible enemy that you can't ignore. After testing, you find that the enemy's X position is at 0x1000, and its Y position is at 0x1004. You also find the screen's X position at 0x2008 and Y position at 0x200C. The enemy's position on screen is given by ( addr(0x1000)-addr(0x2008) , addr(0x1004)-addr(0x200C) ), which indicates the top-left corner of the enemy's hitbox. Suppose the enemy is 15 pixels high and 7 pixels wide. Then the following draws an approximate hitbox:

 local eX, eY, sX, sY
 local hbX, hbY

 while true do
  eX=memory.readdword(0x1000)
  eY=memory.readdword(0x1004)
  sX=memory.readdword(0x2008)
  sY=memory.readdword(0x200C)
  hbX=eX-sX
  hbY=eY-sY
  gui.drawbox(hbX-2, hbY-2, hbX+9, hbY+17)
  emu.frameadvance()
 end
Here is yet another example, which functions as a memory viewer for a range of values:

 local s = memory.readbyterange(0xff00, 20)
 local a, b 

 while true do
 
  for k,v in ipairs(s) do
   a= math.floor((k-1)/10)
   b=(k-1)%10
   gui.text(15*b,10*a,v)
  end
 
  emu.frameadvance()
 
 end

List of drawing functions

In general, these functions don't modify the game in any way. They simply paint over the display with whatever you wanted.

gui.text(x, y, string, color1, color2)

Paints text onto the screen. Typically fixed-width, most emulators use a width of 4 pixels, while DeSmuME uses a width of 6.

color1 applies to the main text color, and it is white if you omit color1. color2 applies to the bordering pixels around the main part of the text, and is black (or dark blue in FCEUX) by default if you omit color2.

FCEUX uses a variable-width text, so the function is modified to return the X position where it stops painting the text. Only important if you need to know the size of the block of text you just threw up there, otherwise just ignore the returned value.

Useful for displaying calculated values that RAM Watch alone can't help you with, or providing various bits of other information in text form.

gui.pixel(x, y, color)

Paints a single pixel. Usually, a single pixel tends to go unnoticed, but there are a few uses for painting individual pixels rather than full lines or boxes.

Good for some sort of minimap script or any form of display requiring fine detail.

gui.line(x1, y1, x2, y2, color)

Paints a straight line starting from one coordinate and ending on the other coordinate. The line is always one pixel wide.

Generally good for showing the angle where things are moving (if you can calculate them in lua!), as well as turning a number into a visible bar for an intuitive feel rather than a concrete feel.

gui.box(x1, y1, x2, y2, FillColor, BorderColor)

Paints a filled-in box, using one color for the border and another color to fill. Omitting BorderColor means the FillColor is used for the border, and a more translucent version of FillColor is used for the fill.

Useful for when you've found the hitbox data in RAM, and want to actually paint them. Also helpful in making other bits of drawing more visible when you first fill a section of the screen with a translucent black, for example.

x y coordinates

You do not need to use static, unchanging values for your x and y. However, gui.text(5,12,"hello world") is certainly very easy to use. But there are ways to move the pieces of display around as needed.

You can fetch some values from RAM (say, the enemy's x,y position), and then use them as the x,y coordinate in your drawing function so that, for example, the text related to the enemy follows them where they go.

Colors

There are several representations of colors to use:

  • Number - 0xrrggbbaa format
    Most emulators have the 80Red glitch that makes drawing using a red value of 80 or higher completely invisible.
  • String - "#rrggbb" or "#rrggbbaa"
    There are also a number of simple colors as well, using strings like "red" or "green".
  • Table - {r = 0xrr, g = 0xgg, b = 0xbb, a = 0xaa} or {0xrr,0xgg,0xbb,0xaa}
    If you prefer, you can use straight decimal numbers, which range from 0 to 255 instead of the hex values that range from 0x00 to 0xFF. The other formats don't easily allow use of decimal numbers like tables.

80Red glitch: When using a number as your color, any value above 0x7FFFFFFF simply fails. No drawing is done, as though the function didn't do anything. However, this only affects the number form -- String and Table are unaffected. You can use negative numbers to reach the brighter reds, but you really need to know what you're doing to get the color you want.

Note: This is how snes9x-based lua implementation works with colors. Newer emulators like lsnes and Bizhawk handle the colors differently, see their docs.


TODO: Teach us to draw things without one-frame delay.
FatRatKnight: You realize... That depends on emulator implementation. Snes9x is impossible as it stands as far as memory reading goes (can paint other things immediately, but memory functions work late regardless of register), but other than that, using gui.register instead of emu.frameadvance usually gets rid of the one-frame delay. Shouldn't be too hard to explain, but I'm not in the mood to write that up now.



Combined RSS Feed
LuaScripting/Display last edited by feos on 2017-04-29 11:31:59
Page info and history | Latest diff | List referrers | View Source