1 2
7 8 9
13 14
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
I have no idea what went wrong, but for some reason on DeSmuME 0.9.9: Download display.lua
Language: lua

day = 0x212DE58 player = {x = 0x21347FC, y = 0x2134804, camx = 0x2134820, camy = 0x2134822} --camx in terms of upright view; same for camy. so camx looks up/down regret1 = {x = 0x2133828, y = 0x2133830, angle = 0x213384C} regret2 = {x = 0x2133AD8, y = 0x2133AE0, angle = 0x2133AFC} --the following when moving forward --camy = 12.000 x decreases --camy = 4.000 x increases --camy = 8.000 y decreases --camy = 0.000 y increases tempx = 0 tempy = 0 bestx = 0 besty = 0 --Declarations here function fn() playerx = memory.readlongsigned(player.x)/4096.0 playery = memory.readlongsigned(player.y)/4096.0 playercamx = memory.readshort(player.camx)/4096.0 playercamy = memory.readshort(player.camy)/4096.0 regret1x = memory.readlongsigned(regret1.x)/4096.0 regret1y = memory.readlongsigned(regret1.y)/4096.0 regret2x = memory.readlongsigned(regret2.x)/4096.0 regret2y = memory.readlongsigned(regret2.y)/4096.0 frame = movie.framecount()+17 --since dsm header is 17 lines gui.drawtext(0,0,"Player: ("..string.format('%.6f',playerx)..","..string.format('%.6f',playery)..")") gui.drawtext(0,10,"Camera: ("..string.format('%.6f',playercamx)..","..string.format('%.6f',playercamy)..")") gui.drawtext(0,20,"Regret 1: ("..string.format('%.6f',regret1x)..","..string.format('%.6f',regret1y)..")") gui.drawtext(0,30,"Regret 2: ("..string.format('%.6f',regret2x)..","..string.format('%.6f',regret2y)..")") gui.drawtext(0,40,"Frame: .dsm "..frame) stylus.set({x=127,y=95,touch=true}) if (frame-17 == 31940 and tempx ~= playerx) then --change to playery if needed; same with frame tempx = playerx tempy = playery end if (frame-17 == 31940 and (tempx < bestx or bestx == 0)) then --change to playery if needed; same with frame; same with sign bestx = tempx besty = tempy end gui.drawtext(0,50,"Player best: ("..string.format('%.6f',bestx)..","..string.format('%.6f',besty)..")") gui.drawtext(0,60,"Player now: ("..string.format('%.6f',tempx)..","..string.format('%.6f',tempy)..")") end gui.register(fn)
Around every 5-15 frames, stylus.set({x=127,y=95,touch=true}) doesn't touch the screen for 1 frame. How do I make it hold forever?
Noxxa
They/Them
Moderator, Expert player (4107)
Joined: 8/14/2009
Posts: 4089
Location: The Netherlands
You're using gui.register(), which has no guarantee of being called every frame (especially not if frame skipping is on). Try putting the stylus function in a while loop with emu.frameadvance() instead, or try using emu.registerbefore() instead.
http://www.youtube.com/Noxxa <dwangoAC> This is a TAS (...). Not suitable for all audiences. May cause undesirable side-effects. May contain emulator abuse. Emulator may be abusive. This product contains glitches known to the state of California to cause egg defects. <Masterjun> I'm just a guy arranging bits in a sequence which could potentially amuse other people looking at these bits <adelikat> In Oregon Trail, I sacrificed my own family to save time. In Star trek, I killed helpless comrades in escape pods to save time. Here, I kill my allies to save time. I think I need help.
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
Thanks very much. It works now. :) Another problem I have, this time for BizHawk: Post #432468
zeromus wrote:
I've explained to you why gui.text is different a billion times. It just is different, and will always be. It creates a label through the same system that the FPS and other stuff uses. If you don't like that, there's other functions for drawing text. If you dont like those other functions for drawing text, then write a bug about why they're deficient.
Can someone please elaborate other ways to show lua display txt/lua values on avi? Right now, for certain games, I can use memory.write to change displays to say, show speed instead, but it cannot be done for arbitrary games.
Post subject: I found an eleven-foot pole
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
memory.write won't work for other games because it's poking values into ram as opposed to drawing on the display surface. On that note, you probably shouldn't be using memory.write at all unless you're trying science. Can you post the relevant lua script? I cannot read a sealed book. I can't help with something I can't see.
Adventures in Lua When did I get a vest?
Post subject: Re: I found an eleven-foot pole
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
Pokota wrote:
memory.write won't work for other games because it's poking values into ram as opposed to drawing on the display surface. On that note, you probably shouldn't be using memory.write at all unless you're trying science. Can you post the relevant lua script? I cannot read a sealed book. I can't help with something I can't see.
Sure. It's this script. I want to make an encode showing me reaching 100,000+ speed, but the lua gui.text doesn't show up on encode. Edit: Display OSD does not work btw.
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
1: Per the encoders, you need gui.drawText as opposed to gui.text 2: Go to File > AVI/WAV > Capture OSD, and make sure it's checked. Nothing will be captured for encode otherwise.
Adventures in Lua When did I get a vest?
Spikestuff
They/Them
Editor, Publisher, Expert player (2630)
Joined: 10/12/2011
Posts: 6435
Location: The land down under.
Using your code, modifying it with what Pokota advised:
WebNations/Sabih wrote:
+fsvgm777 never censoring anything.
Disables Comments and Ratings for the YouTube account. Something better for yourself and also others.
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
Sorry to bump, but in terms of BizHawk, what would be better? Having a script with tons of features all packed into one, or make them into separate lua files and run them simultaneously? Talking in terms of performance and manage-ability.
Post subject: When reading input from the keyboard...
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
input.get uses capital letters for its keys, and Lua will look up the key exactly as you type it. F is different from f.
local keys = {}

while true do 
	keys = input.get()
	if (keys.F == true) then 
		console.log("You pressed F!")
	end 
	emu.frameadvance() 
end 
jlun: I prefer doing separate files for separate functions, and then having one core file that gets passed to the lua console. Different people will make different recommendations.
Adventures in Lua When did I get a vest?
Former player
Joined: 4/18/2015
Posts: 168
Location: Canada
Hey guys, I'm a complete noob when it comes to this scripting stuff, but I would like to create one that can show character and enemy hitboxes in the NES game Balloon Fight. The real problem I have is that I have no idea how I would find the hitbox values in the games code....Anyone know what I can do?
Samsara
She/They
Senior Judge, Site Admin, Expert player (2098)
Joined: 11/13/2006
Posts: 2821
Location: Northern California
A lot of games handle hitboxes differently, so you may have to do some deeper research to figure it out for a specific game. The only way I really know about is via looking up the X/Y position for your character. The GBA Castlevania games, at the very least, store the corners of the hitbox in 4 separate addresses, so if you find those addresses (which would change alongside X/Y position) then you can draw lines between those points via a Lua script. But again, other games do it differently, so you'll have to look deeper into it. There's a RAM map on Datacrystal, that could be a good start.
TASvideos Admin and acting Senior Judge 💙 Currently unable to dedicate a lot of time to the site, taking care of family. Now infrequently posting on Bluesky
warmCabin wrote:
You shouldn't need a degree in computer science to get into this hobby.
Former player
Joined: 4/18/2015
Posts: 168
Location: Canada
Samsara wrote:
A lot of games handle hitboxes differently, so you may have to do some deeper research to figure it out for a specific game. The only way I really know about is via looking up the X/Y position for your character. The GBA Castlevania games, at the very least, store the corners of the hitbox in 4 separate addresses, so if you find those addresses (which would change alongside X/Y position) then you can draw lines between those points via a Lua script. But again, other games do it differently, so you'll have to look deeper into it. There's a RAM map on Datacrystal, that could be a good start.
Thanks Samsara, that really helped out a lot! :D With that knowledge, I'm 99% sure I discovered player 1's hitbox walls. The values are as follows: X: 0091 Hitbox Left: 022B Hitbox Right: 022F Y: 009A Hitbox Top: 0224 Hitbox Bottom: 0228 Now, how would I write a script that simply makes a coloured hitbox out of those points?
Samsara
She/They
Senior Judge, Site Admin, Expert player (2098)
Joined: 11/13/2006
Posts: 2821
Location: Northern California
Language: Lua

while true do gui.drawBox(mainmemory.read_u8(0x022F), mainmemory.read_u8(0x0228),mainmemory.read_u8(0x022B), mainmemory.read_u8(0x0224), "white") emu.frameadvance(); end
Super messy code, but gets the job done. The order of the values matters, I.E you want to do something like Left, Bottom, Right, Top in order to get the box where you want it. White can be changed to any other color. EDIT: The box seems to be a bit off, so you may want to substitute the addresses you found for some other potential addresses and see if you can get something closer. EDIT2: That explains it. It's graphic data, not the hitbox. It's definitely using a different method of figuring out the hitboxes. I'll look into it, but I don't promise any results anytime soon. I'm super new to this too...
TASvideos Admin and acting Senior Judge 💙 Currently unable to dedicate a lot of time to the site, taking care of family. Now infrequently posting on Bluesky
warmCabin wrote:
You shouldn't need a degree in computer science to get into this hobby.
Former player
Joined: 4/18/2015
Posts: 168
Location: Canada
Samsara wrote:
Language: Lua

while true do gui.drawBox(mainmemory.read_u8(0x022F), mainmemory.read_u8(0x0228), mainmemory.read_u8(0x022B), mainmemory.read_u8(0x0224),"white") emu.frameadvance(); end
Super messy code, but gets the job done. The order of the values matters, I.E you want to do something like Left, Bottom, Right, Top in order to get the box where you want it. White can be changed to any other color. EDIT: The box seems to be a bit off, so you may want to substitute the addresses you found for some other potential addresses and see if you can get something closer. EDIT2: That explains it. It's graphic data, not the hitbox. It's definitely using a different method of figuring out the hitboxes. I'll look into it, but I don't promise any results anytime soon. I'm super new to this too...
Oh ya, you're right. I've been trying to find the hitbox data but it doesn't seem to be anywhere. If the hitbox data was even found, could it shown visually using lua?
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
It could definitely, it's just a matter of
  1. finding it, then
  2. understanding it.
It's possible that the game doesn't directly store the hitbox's rect, but rather just a coordinate pair that can be used to derive the hitbox. Regardless of how, we would just use whatever we discover about the game as the basis for painting the hitbox using lua.
Adventures in Lua When did I get a vest?
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
This has been driving me nuts for a while so:
Language: lua

memory.usememorydomain("IWRAM") local frame_start = emu.framecount() function waitframe(frame) while (frame >= 0) do emu.frameadvance() frame = frame-1 end end --[[Start the script as you select an opponent to attack Algorithim: 1. Record current frame 2. Make a savestate 3. Press "A" 4. Advance 100 frames 5. Check to see if attack hit/missed. If missed, skip to step 7. If hit proceed to next step 6. Check if it had critical hit. If so stop. If not, continue. Don't check if move is ID 0xC4 7. Load the state 8. Advance 1 frame 9. Repeat from step 2 ]]-- while true do local frame_now = emu.framecount() local frame_dif = 0 --difference between this frame and start frame if (frame_dif ~= (frame_now - frame_start)) then savestate.saveslot(1) end joypad.set({A = 1}) emu.frameadvance() waitframe(500) console.log("Reached 1") --if memory.readbyte(0x2C14) == 61 then if memory.readbyte(0x2A0B) > 0 then --damage you deal break else frame_dif = frame_dif + 1 savestate.loadslot(1) console.log("Dif"..(frame_now - frame_start)) console.log("Frame now: "..frame_now) console.log("Reached 2") end emu.frameadvance() end
Specifically this part:
else
		frame_dif = frame_dif + 1
		savestate.loadslot(1)
		console.log("Dif"..(frame_now - frame_start))
		console.log("Frame now: "..frame_now)
		console.log("Reached 2")
	end
Maybe I'm going nuts, or just my computer, but I swear whenever I remove or comment out the console sections, savestate.loadslot(1) never triggers. It oddly works fine (ignoring the console spam) if I add it back however. Also please ignore the comment on critical hits; I haven't implemented it yet.
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
Try removing the console.log() calls one at a time to see which one it breaks on. It seems to be working fine on my end.
Adventures in Lua When did I get a vest?
Skilled player (1736)
Joined: 9/17/2009
Posts: 4979
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
I'm not sure how useful this is to anyone, but since I already wrote it: Assigning discrete values to an array in a single line:
Language: lua

local Array = {} --Because some of the values are all over the place function assign_multi(string,...) for i, v in ipairs(arg) do Array[v] = string end end assign_multi("Village",20,46,97,131,132) assign_multi("Forest",2,3,133) assign_multi("Village 2",88,141,166,168)
Checking if a key in keyboard has been pressed, then react after a set amount of frames: Download inputdelay.lua
Language: lua

function inputdelay(inputs, delay) local is = input.get() local start = false if inputs ~= nil and (is[tostring(inputs)] ~=nil) then console.log(is) while (delay > 0) do emu.frameadvance() delay = delay -1 end start = true return start end return start end while true do if inputdelay("C",30) then gui.text(0,0,"Hi") --debugging end emu.frameadvance() end
Mouse input checking: Download mouse.lua
Language: lua

--checks the mouse position during a click relative to the client and see if its within some range function get_mouse_pos(x,y,width,height) mx = input.getmouse().X my = input.getmouse().Y --input.getmouse() is already relative to client if (mx > x and mx <x> y and my < y+height) then return true end return false end while true do if (input.getmouse().Left and get_mouse_pos(0,50,80,20)) then gui.drawText(0,30,"Hello") end emu.frameadvance() end
Pokota
He/Him
Joined: 2/5/2014
Posts: 779
Thanks to Zeromus, I now understand that event.onMemoryFoo uses system bus addressing instead of domain-relative addressing. This means that to use onMemoryFoo, we need to know our memory bank offsets.
Language: lua

address = 0x09c75c memory_bank_offset = 0x80000000 function setAeri() console.log("function setAeri was called") value = mainmemory.read_u8(address) if (value >= 0x100) then newvalue = value % 256 setAeri(newvalue) elseif (value >=0) then timer = bit.band(value,1) if timer == 0 then mainmemory.write_u8(0x09C8D8, 0x54) mainmemory.write_u8(0x09C8D9, 0x48) elseif timer == 1 then mainmemory.write_u8(0x09C8D8, 0x53) mainmemory.write_u8(0x09C8D9, 0xFF) else console.log("timer is neither odd nor even") end else console.log("was passed a negative value") end end event.onmemorywrite(function() setAeri() end, memory_bank_offset + address, "setAeri")
this script will change Aeri from Aeris to Aerith, depending on whether Cloud's number of kills is even or odd.
Adventures in Lua When did I get a vest?
Site Admin, Skilled player (1250)
Joined: 4/17/2010
Posts: 11468
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
I need a guru. Here's my script for Genesis Gargoyles that draws BG. Right now, I'm using a 1D array of "structs", with each unit representing a block with its attributes, and if the block is offscreen, its unit becomes nil, if it's onscreen, its unit gets calculated and cached. I'm also iterating through the whole map, and the array I create that way is very huge. This results in low speed of the function. This is all happening in function Background(), the rest don't seem to matter too much. Can someone help me to speed this thing up? If I make a consecutive array instead of index=x+y*mapwidth, it draws only the first bunch of blocks in the map. There's also some garbage left from camhack and size divider, ignore that. Download Gargoyles.lua
Language: lua

-- Gargoyles, Genesis (BizHawk) -- feos, 2015-2016 --== Shortcuts ==-- rb = memory.read_u8 rw = memory.read_u16_be rws = memory.read_s16_be r24 = memory.read_u24_be rl = memory.read_u32_be box = gui.drawBox text = gui.pixelText line = gui.drawLine AND = bit.band SHIFT = bit.rshift --== RAM addresses ==-- levnum = 0xff00ba LevelFlr = 0xff00c0 LevelCon = 0xff00c4 mapline_tab = 0xff0244 GlobalBase = 0xff1c76 GolBase = 0xff2c76 MapA_Buff = 0xff4af0 --== Camera Hack ==-- camhack = false div = 1 -- scale size = 16/div -- block size --== Other stuff ==-- XposLast = 0 YposLast = 0 room = 0 workinglast = 0 lagcount = emu.lagcount() gui.defaultPixelFont("fceux") --== Block cache ==-- cache = {} function main() rnd1 = rl (0xff001c) rnd2 = rw (0xff0020) working = rb (0xff0073) xblocks = rw (0xff00d4) mapw = rw (0xff00d4)*8 maph = rw (0xff00d6)*8 Xpos = rws(0xff0106) Ypos = rws(0xff0108) camx = rws(0xff010c)+16 camy = rws(0xff010e)+16 run = rb (0xff1699) inv = rw (0xff16d2) health = rws(0xff2cc6) backx = camx backy = camy Xspd = Xpos-XposLast Yspd = Ypos-YposLast XposLast = Xpos YposLast = Ypos facing = AND(rb(GolBase+0x48),2) -- object flag 1 Background() rndlast = rnd1 workinglast = working end function Background() if working>0 then return end local border = -16 local basex = camx+border local basey = camy+border local boundx = 320-border local boundy = 224-border -- xblocks = ((camx+boundx+32)-(basex-32))/16 for i = PosToIndex(basex-32,basey-32), PosToIndex((camx+boundx+32),(camy+boundy+32)) do local pos = IndexToPos(i) if InBounds(pos.x,basex-32,camx+boundx+32) then local unit = cache[i] if unit == nil or workinglast>0 then if InBounds(pos.x,basex,camx+boundx) and InBounds(pos.y,basey,camy+boundy) then cache[i] = GetBlock(pos.x,pos.y) end else if not InBounds(pos.x,basex,camx+boundx) and not InBounds(pos.y,basey,camy+boundy) then cache[i] = nil end end if unit ~= nil then DrawBG(unit, pos) end elseif cache[i] ~= nil then cache[i] = nil end end end function DrawBG(unit, pos) local val = unit.block local x1 = pos.x/div-camx/div local x2 = x1+size-1 local y1 = pos.y/div-camy/div local y2 = y1+size-1 local col = 0 -- block color local opout = 0x33000000 -- outer opacity local opin = 0x66000000 -- inner opacity local op = 0xff000000 if unit.contour ~= nil then box(x1,y1,x2,y2,0x5500ff00,0x5500ff00) for pixel=0,15 do if unit.contour[pixel]>0 then gui.drawPixel( x1+pixel/div, y1+unit.contour[pixel]/div-1/div, 0xffffff00) end end end if val>0 then if val==0x80 then -- WALL col = 0x00ffffff -- white line(x1,y1,x1,y2,col+op) -- left line(x2,y1,x2,y2,col+op) -- right elseif val==0x81 then -- CEILING col = 0x00ffffff -- white line(x1,y2,x2,y2,col+op) -- bottom elseif val==0x82 then -- CLIMB_U col = 0x0000ffff -- cyan line(x1,y2,x2,y2,col+op) -- bottom elseif val==0x83 then -- CLIMB_R col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left elseif val==0x84 then -- CLIMB_L col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right elseif val==0x85 then -- CLIMB_LR col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left line(x2,y1,x2,y2,col+op) -- right elseif val==0x86 then -- CLIMB_R_STAND_R col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left elseif val==0x87 then -- CLIMB_L_STAND_L col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right elseif val==0x87 then -- CLIMB_LR_STAND_LR col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x00ff00ff -- cyan line(x1,y1,x1,y2,col+op) -- left col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right elseif val==0x70 then -- GRAB_SWING col = 0x0000ff00 -- green box(x1,y1,x2,y2,col,col+opout) elseif val==0x7f then -- EXIT col = 0x00ffff00 -- yellow elseif val==0xd0 or val==0xd1 then -- SPIKES col = 0x00ff0000 -- red box(x1,y1,x2,y2,col,col+opout) else -- LEVEL_SPECIFIC col = 0x00ff8800 -- orange box(x1,y1,x2,y2,col+opin,col+opout) end box(x1,y1,x2,y2,col+opin,col+opout) end end function GetBlock(x,y) if working>0 then return nil end local final = { contour={}, block=0 } if x>0 and x<mapw and y>0 and y<maph then local x1 = x/div-camx/div local x2 = x1+size-1 local y1 = y/div-camy/div local y2 = y1+size-1 local d4 = rw(mapline_tab+SHIFT(y,4)*2) local a1 = r24(LevelFlr+1) local d1 = SHIFT(rw(MapA_Buff+d4+SHIFT(x,4)*2),1) final.block = rb(a1+d1+2) d1 = rw(a1+d1) a1 = r24(LevelCon+1)+d1 if rb(a1)>0 or rb(a1+8)>0 then for pixel=0,15 do final.contour[pixel] = rb(a1+pixel) end else final.contour = nil end else return nil end return final end function PosToIndex(x,y) return math.floor(x/16)+math.floor(y/16)*xblocks end function IndexToPos(i) return { x=(i%xblocks)*16, y=math.floor(i/xblocks)*16 } end function InBounds(x,minimum,maximum) if x>=minimum and x<=maximum then return true else return false end end while true do main() emu.frameadvance() end
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Editor, Skilled player (1198)
Joined: 9/27/2008
Posts: 1085
Been a while since I last did any lua scripting. Had water damage to my previous laptop, and haven't set this new one up with TAS tools.
Language: lua

for i = PosToIndex(basex-32,basey-32), PosToIndex((camx+boundx+32),(camy+boundy+32)) do
Let me see if I'm reading that right... Start: PosToIndex(basex-32,basey-32) End: PosToIndex((camx+boundx+32),(camy+boundy+32)) Step: 1 (implied) So this loop iterates from the index of the top-left corner to the index of the bottom-right corner. However wide the map is, this will also catch every tile to the right and left of our target region, except for the top row off the left and the bottom row off the right. I think I see why the checks for in-bounds, which do add a bit more processing.
Language: lua

local x1, x2= math.floor(camx/16), math.floor((camx+319)/16) local y1, y2= math.floor(camy/16), math.floor((camy+223)/16) for yTile= y1, y2 do for xTile= x1, x2 do local i= xTile + yTile*xblock
One quick thought here. You have your index, and which tile by x and y. You're also only looking at in-bounds tiles, so we shouldn't need checks for out of bounds. Well, aside from strange cases where the camera shouldn't be. I don't happen to know whether the for loop would call the ending parameter multiple times. The PosToIndex((camx+boundx+32),(camy+boundy+32)) part. I set a variable above the loop and use that rather than the call to be sure it fetches the value and not make function calls every loop. Again, a habit of mine to be sure.
Language: lua

if unit == nil or workinglast>0 then if (unit == nil) or (workinglast>0) then
I have bad memories about the order of operations involving compare and logical operations. From now on, I bracket the compares I want done in parentheses, just in case. I don't know how well my bad memories apply here, but I use parentheses readily for my own sanity. As for your cache, every time the working variable resets to zero (working == 0 ; workinglast ~= 0), assign an empty table to cache. Don't even bother trying to blank out the individual elements, let the garbage collector kill the older table. I'm assuming the map doesn't modify itself as long as working remains zero.
Language: lua

cache[i]= cache[i] or GetBlock(x,y) if cache[i] then
The or logical operation will have a value equal to the left parameter if it is not false or nil, and won't even run the second parameter in that case. If the left parameter turns out to be false or nil, then it will use the second parameter, making the function calls if any exist there. It's a nifty shortcut to remember.
Language: lua

if val>0 then if val==0x80 then -- WALL col = 0x00ffffff -- white line(x1,y1,x1,y2,col+op) -- left line(x2,y1,x2,y2,col+op) -- right elseif val==0x81 then -- CEILING col = 0x00ffffff -- white line(x1,y2,x2,y2,col+op) -- bottom --And so on
I don't like a chain of elseifs in my code. I do whatever I can to turn it into a table instead:
Language: lua

local white = 0x00ffffff local orange= 0x00ff8800 local opout = 0x33000000 -- outer opacity local opin = 0x66000000 -- inner opacity local op = 0xff000000 local DrawTile= { [0]= function() end, --do nothing if zero [0x80]= function(x1,y1,x2,y2) line(x1,y1,x1,y2,white+op) -- left line(x2,y1,x2,y2,white+op) -- right end, --and so on } local function DefaultDrawTile(x1,y1,x2,y2) box(x1,y1,x2,y2,col+opin,col+opout) end --Pretend we're in your function now local Fn= DrawTile[i] or DefaultDrawTile Fn(x1,y1,x2,y2)
I've got just these thoughts to start. If it's still not fast enough, I can see if I can reduce the code further. These are all the points that strike out at me right now.
Site Admin, Skilled player (1250)
Joined: 4/17/2010
Posts: 11468
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
Thanks! I do the bounds check there to see if I should remove the block that went offscreen or add the new block I scrolled in. There's a check area around the screen borders. Array of functions doesn't seem to speed me up at all. But switching to 2 nested loops saved me some 10 fps. What I had in mind was some way to stop using such a huge array of cache entries, but I still don't know how. Download Gargoyles.lua
Language: lua

-- Gargoyles, Genesis (BizHawk) -- feos, 2015-2016 --== Shortcuts ==-- rb = memory.read_u8 rw = memory.read_u16_be rws = memory.read_s16_be r24 = memory.read_u24_be rl = memory.read_u32_be box = gui.drawBox text = gui.pixelText line = gui.drawLine AND = bit.band SHIFT = bit.rshift --== RAM addresses ==-- levnum = 0xff00ba LevelFlr = 0xff00c0 LevelCon = 0xff00c4 mapline_tab = 0xff0244 GlobalBase = 0xff1c76 GolBase = 0xff2c76 MapA_Buff = 0xff4af0 --== Camera Hack ==-- camhack = false div = 1 -- scale size = 16/div -- block size --== Other stuff ==-- XposLast = 0 YposLast = 0 room = 0 workinglast = 0 lagcount = emu.lagcount() gui.defaultPixelFont("fceux") --== Block cache ==-- cache = {} function main() rnd1 = rl (0xff001c) rnd2 = rw (0xff0020) working = rb (0xff0073) xblocks = rw (0xff00d4) mapw = rw (0xff00d4)*8 maph = rw (0xff00d6)*8 Xpos = rws(0xff0106) Ypos = rws(0xff0108) camx = rws(0xff010c)+16 camy = rws(0xff010e)+16 run = rb (0xff1699) inv = rw (0xff16d2) health = rws(0xff2cc6) backx = camx backy = camy Xspd = Xpos-XposLast Yspd = Ypos-YposLast XposLast = Xpos YposLast = Ypos facing = AND(rb(GolBase+0x48),2) -- object flag 1 Background() rndlast = rnd1 workinglast = working end function Background() if working>0 then cache = {} return end local border = 0 local offset = 32 local basex = camx+border local basey = camy+border local basei = PosToIndex(basex-offset,basey-offset) local boundx = 320-border local boundy = 224-border local xblockstockeck = ((camx+boundx+offset)-(basex-offset))/16 local yblockstockeck = ((camy+boundy+offset)-(basey-offset))/16 for yblock = 0,yblockstockeck do for xblock = 0,xblockstockeck do local i = yblock*xblocks+xblock+basei local x=basex+xblock*16 local y=basey+yblock*16 if InBounds(x,basex-offset,camx+boundx+offset) then local unit = cache[i] if unit == nil or workinglast>0 then if InBounds(x,basex,camx+boundx) and InBounds(y,basey,camy+boundy) then cache[i] = GetBlock(x,y) end else if not InBounds(x,basex,camx+boundx) and not InBounds(y,basey,camy+boundy) then cache[i] = nil end end if unit ~= nil then DrawBG(unit,x,y) end elseif cache[i] ~= nil then cache[i] = nil end end end end col = 0 -- block color opout = 0x33000000 -- outer opacity opin = 0x66000000 -- inner opacity op = 0xff000000 DrawBlock = { [0x80] = function(x1,y1,x2,y2) -- WALL col = 0x00ffffff -- white line(x1,y1,x1,y2,col+op) -- left line(x2,y1,x2,y2,col+op) -- right end, [0x81] = function(x1,y1,x2,y2) -- CEILING col = 0x00ffffff -- white line(x1,y2,x2,y2,col+op) -- bottom end, [0x82] = function(x1,y1,x2,y2) -- CLIMB_U col = 0x0000ffff -- cyan line(x1,y2,x2,y2,col+op) -- bottom end, [0x83] = function(x1,y1,x2,y2) -- CLIMB_R col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left end, [0x84] = function(x1,y1,x2,y2) -- CLIMB_L col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right end, [0x85] = function(x1,y1,x2,y2) -- CLIMB_LR col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left line(x2,y1,x2,y2,col+op) -- right end, [0x86] = function(x1,y1,x2,y2) -- CLIMB_R_STAND_R col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x0000ffff -- cyan line(x1,y1,x1,y2,col+op) -- left end, [0x87] = function(x1,y1,x2,y2) -- CLIMB_L_STAND_L col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right end, [0x88] = function(x1,y1,x2,y2) -- CLIMB_LR_STAND_LR col = 0x00ffffff -- white line(x1,y1,x2,y1,col+op) -- top col = 0x00ff00ff -- cyan line(x1,y1,x1,y2,col+op) -- left col = 0x0000ffff -- cyan line(x2,y1,x2,y2,col+op) -- right end, [0x70] = function(x1,y1,x2,y2) -- GRAB_SWING col = 0x0000ff00 -- green box(x1,y1,x2,y2,col,col+opout) end, [0x7f] = function(x1,y1,x2,y2) -- EXIT col = 0x00ffff00 -- yellow end, [0xd0] = function(x1,y1,x2,y2) -- SPIKES col = 0x00ff0000 -- red box(x1,y1,x2,y2,col,col+opout) end, [0xd1] = function(x1,y1,x2,y2) -- SPIKES col = 0x00ff0000 -- red box(x1,y1,x2,y2,col,col+opout) end } function DrawBlockDefault(x1,y1,x2,y2)-- LEVEL_SPECIFIC col = 0x00ff8800 -- orange box(x1,y1,x2,y2,col+opin,col+opout) end function DrawBG(unit, x, y) local val = unit.block local x1 = x/div-camx/div-camx%size local x2 = x1+size-1 local y1 = y/div-camy/div-camy%size local y2 = y1+size-1 if unit.contour ~= nil then box(x1,y1,x2,y2,0x5500ff00,0x5500ff00) for pixel=0,15 do if unit.contour[pixel]>0 then gui.drawPixel( x1+pixel/div, y1+unit.contour[pixel]/div-1/div, 0xffffff00) end end end if val>0 then local Fn = DrawBlock[val] or DrawBlockDefault Fn(x1,y1,x2,y2) box(x1,y1,x2,y2,col+opin,col+opout) end end function GetBlock(x,y) if working>0 then return nil end local final = { contour={}, block=0 } if x>0 and x<mapw>0 and y<maph>0 or rb(a1+8)>0 then for pixel=0,15 do final.contour[pixel] = rb(a1+pixel) end else final.contour = nil end else return nil end return final end function PosToIndex(x,y) return math.floor(x/16)+math.floor(y/16)*xblocks end function IndexToPos(i) return { x=(i%xblocks)*16, y=math.floor(i/xblocks)*16 } end function InBounds(x,minimum,maximum) if x>=minimum and x<=maximum then return true else return false end end while true do main() emu.frameadvance() end
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
Editor, Skilled player (1198)
Joined: 9/27/2008
Posts: 1085
Well, if you're going to keep a cache of the current screen, you will have no fewer than (320/16) * (224/16), or 280 elements. Slightly more, as the screen moves and shows parts of some lines at the edges. If you allow the display to scale out, a 0.5x zoom has this at 1120, provided the zoom stays entirely within map bounds. Reducing this number doesn't look feasible to me. Any particular reason for clearing the screen edges you're leaving from? I don't know the internals that lua goes through to locate table elements, but I'm hoping the search is fast enough to not care too much how many things are in that table. I'm also looking at GetBlock to see if there's any speed-ups I can do relating to it.
CrazyTerabyte
He/Him
Joined: 5/7/2005
Posts: 74
FatRatKnight wrote:
[…] I don't know the internals that lua goes through to locate table elements, but I'm hoping the search is fast enough to not care too much how many things are in that table. […]
Lua tables behave as both array/lists and as hash_map/dict. And the Lua interpreter is supposed to be smart enough to use the best implementation based on your usage. I don't know enough of the internals, but I wouldn't worry about the cache size, as it shouldn't affect performance. (Unless it starts eating too much memory, or if you try to list all of its elements; then the size matters.) Another idea: You may look at http://lua-users.org/wiki/ProfilingLuaCode to use a profiler to identify performance bottlenecks on your code.
Site Admin, Skilled player (1250)
Joined: 4/17/2010
Posts: 11468
Location: Lake Char­gogg­a­gogg­man­chaugg­a­gogg­chau­bun­a­gung­a­maugg
GetBlock() is insanely slow, because for every action, there's a call from C# to lua core written in C, that generates a ton of overhead compared to the same function running in Gens. For whatever reason, not wiping the offscreen data (and dropping all the bounds checks) slows me down a few fps. The reason I keep mentioning the size of the resulting table is because it does slow the emu down if the level map is too wide, even if I don't even iterate through all the entries.
Warning: When making decisions, I try to collect as much data as possible before actually deciding. I try to abstract away and see the principles behind real world events and people's opinions. I try to generalize them and turn into something clear and reusable. I hate depending on unpredictable and having to make lottery guesses. Any problem can be solved by systems thinking and acting.
1 2
7 8 9
13 14