Stage rulers. Try to hijack algorithm used to determine tile location

of characters.
This commit is contained in:
Empathic Qubit 2021-03-03 15:40:19 -05:00
parent c63f6a9325
commit ee2198b93f
9 changed files with 1687 additions and 1542 deletions

5
.editorconfig Normal file
View file

@ -0,0 +1,5 @@
[*]
indent_size = 4
indent_style = space
charset = utf-8
end_of_line = lf

4
.gitignore vendored
View file

@ -1,3 +1,5 @@
*.lsmv
*.log *.log
catchem/ catchem/
state/
crashsave*
*.backup

View file

@ -1,22 +1,19 @@
local _M = {} local _M = {}
--[[ --[[
Change BizhawkDir to your BizHawk directory. Change script dir to your script directory
--]] --]]
--_M.BizhawkDir = "C:/Users/mmill/Downloads/BizHawk-2.2/" _M.ScriptDir = "/media/removable/Main/user1000/neat-donk"
_M.BizhawkDir = "X:/B2_BizHawkLab/BizHawk-2.2.2/"
_M.StateDir = _M.BizhawkDir .. "Lua/SNES/neat-mario/state/" _M.StateDir = _M.ScriptDir .. "/state/"
_M.PoolDir = _M.BizhawkDir .. "Lua/SNES/neat-mario/pool/" _M.PoolDir = _M.ScriptDir .. "/pool/"
--[[ --[[
At the moment the first in list will get loaded. At the moment the first in list will get loaded.
Rearrange for other savestates. (will be redone soon) Rearrange for other savestates. (will be redone soon)
--]] --]]
_M.State = { _M.State = {
"DP1.state", -- Donut Plains 1 "PiratePanic.lsmv",
"YI1.state", -- Yoshi's Island 1
"YI2.state", -- Yoshi's Island 2
} }
--[[ --[[
@ -51,19 +48,23 @@ MaxNodes = 1000000,
} }
_M.ButtonNames = { _M.ButtonNames = {
"A", "B",
"B", "Y",
"X", "Select",
"Y", "Start",
"Up", "Up",
"Down", "Down",
"Left", "Left",
"Right", "Right",
} "A",
"X",
"L",
"R",
}
_M.BoxRadius = 6 _M.BoxRadius = 6
_M.InputSize = (_M.BoxRadius*2+1)*(_M.BoxRadius*2+1) _M.InputSize = (_M.BoxRadius*2+1)*(_M.BoxRadius*2+1)
_M.Running = false _M.Running = true
return _M return _M

View file

@ -1,63 +1,65 @@
util = require "util"
FG_COLOR = 0x00ffffff
BG_COLOR = 0x99000000
TILEDATA_POINTER = 0x7e0098
TILE_SIZE = 32
TILE_RADIUS = 4
SPRITE_BASE = 0x7e0de2
SOLID_LESS_THAN = 0x7e00a0
DIDDY_X_VELOCITY = 0x7e0e02
DIDDY_Y_VELOCITY = 0x7e0e06
DIXIE_X_VELOCITY = 0x7e0e60
DIXIE_Y_VELOCITY = 0x7e0e64
CAMERA_X = 0x7e17ba
CAMERA_Y = 0x7e17c0
CAMERA_MODE = 0x7e054f
TILE_COLLISION_MATH_POINTER = 0x7e17b2
VERTICAL_POINTER = 0xc414
PARTY_X = 0x7e0a2a
PARTY_Y = 0x7e0a2c
count = 0 count = 0
detailsidx = -1 detailsidx = -1
jumping = false
helddown = false helddown = false
floatmode = false floatmode = false
rulers = true
pokemon = false pokemon = false
pokecount = 0 pokecount = 0
showhelp = false showhelp = false
locked = false locked = false
lockdata = nil lockdata = nil
incsprite = 0 incsprite = 0
fgcolor = 0x00ffffff party_tile_offset = 0
bgcolor = 0x99000000 party_y_ground = 0
function table_to_string(tbl)
local result = "{"
local keys = {}
for k in pairs(tbl) do
table.insert(keys, k)
end
table.sort(keys)
for _, k in ipairs(keys) do
local v = tbl[k]
if type(v) == "number" and v == 0 then
goto continue
end
-- Check the key type (ignore any numerical keys - assume its an array) last_called = 0
if type(k) == "string" then function set_party_tile_offset (val)
result = result.."[\""..k.."\"]".."=" if party_tile_offset_debounce == val then
end return
end
local sec, usec = utime()
last_called = sec * 1000000 + usec
party_tile_offset_debounce = val
end
-- Check the value type function text(x, y, msg)
if type(v) == "table" then gui.text(x, y, msg, FG_COLOR, BG_COLOR)
result = result..table_to_string(v)
elseif type(v) == "boolean" then
result = result..tostring(v)
else
result = result.."\""..v.."\""
end
result = result..",\n"
::continue::
end
-- Remove leading commas from the result
if result ~= "" then
result = result:sub(1, result:len()-1)
end
return result.."}"
end end
function on_keyhook (key, state) function on_keyhook (key, state)
if not helddown and state["value"] == 1 then if not helddown and state.value == 1 then
if key == "1" and not locked then if key == "1" and not locked then
helddown = true helddown = true
detailsidx = detailsidx - 1 detailsidx = detailsidx - 1
if detailsidx < -1 then if detailsidx < -1 then
detailsidx = 20 detailsidx = 22
end end
elseif key == "2" and not locked then elseif key == "2" and not locked then
helddown = true helddown = true
detailsidx = detailsidx + 1 detailsidx = detailsidx + 1
if detailsidx > 20 then if detailsidx > 22 then
detailsidx = -1 detailsidx = -1
end end
elseif key == "3" then elseif key == "3" then
@ -80,89 +82,90 @@ function on_keyhook (key, state)
elseif key == "7" then elseif key == "7" then
helddown = true helddown = true
floatmode = not floatmode floatmode = not floatmode
elseif key == "8" then
helddown = true
rulers = not rulers
elseif key == "0" then elseif key == "0" then
showhelp = true showhelp = true
end end
elseif state["value"] == 0 then elseif state.value == 0 then
helddown = false helddown = false
showhelp = false showhelp = false
end end
end end
function on_input (subframe) function on_input (subframe)
jumping = input.get(0,0) ~= 0
if floatmode then if floatmode then
memory.writebyte(0x7e19ce, 0x16) memory.writebyte(0x7e19ce, 0x16)
memory.writebyte(0x7e0e12, 0x99) memory.writebyte(0x7e0e12, 0x99)
memory.writebyte(0x7e0e70, 0x99) memory.writebyte(0x7e0e70, 0x99)
if input.get(0, 6) == 1 then if input.get(0, 6) == 1 then
memory.writeword(0x7e0e02, -0x5ff) memory.writeword(DIDDY_X_VELOCITY, -0x5ff)
memory.writeword(0x7e0e60, -0x5ff) memory.writeword(DIXIE_X_VELOCITY, -0x5ff)
memory.writeword(0x7e0e06, 0) memory.writeword(DIDDY_Y_VELOCITY, 0)
memory.writeword(0x7e0e64, 0) memory.writeword(DIXIE_Y_VELOCITY, 0)
elseif input.get(0, 7) == 1 then elseif input.get(0, 7) == 1 then
memory.writeword(0x7e0e02, 0x5ff) memory.writeword(DIDDY_X_VELOCITY, 0x5ff)
memory.writeword(0x7e0e60, 0x5ff) memory.writeword(DIXIE_X_VELOCITY, 0x5ff)
memory.writeword(0x7e0e06, 0) memory.writeword(DIDDY_Y_VELOCITY, 0)
memory.writeword(0x7e0e64, 0) memory.writeword(DIXIE_Y_VELOCITY, 0)
end end
if input.get(0, 4) == 1 then if input.get(0, 4) == 1 then
memory.writeword(0x7e0e06, -0x05ff) memory.writeword(DIDDY_Y_VELOCITY, -0x05ff)
memory.writeword(0x7e0e64, -0x05ff) memory.writeword(DIXIE_Y_VELOCITY, -0x05ff)
elseif input.get(0, 5) == 1 then elseif input.get(0, 5) == 1 then
memory.writeword(0x7e0e06, 0x5ff) memory.writeword(DIDDY_Y_VELOCITY, 0x5ff)
memory.writeword(0x7e0e64, 0x5ff) memory.writeword(DIXIE_Y_VELOCITY, 0x5ff)
end end
end end
end end
function file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
function get_sprite(base_addr) function get_sprite(base_addr)
return { return {
["control"] = memory.readword(base_addr), base_addr = string.format("%04x", base_addr),
["draworder"] = memory.readword(base_addr + 0x02), control = memory.readword(base_addr),
["x"] = memory.readword(base_addr + 0x06), draworder = memory.readword(base_addr + 0x02),
["y"] = memory.readword(base_addr + 0x0a), x = memory.readword(base_addr + 0x06),
["jumpheight"] = memory.readword(base_addr + 0x0e), y = memory.readword(base_addr + 0x0a),
["style"] = memory.readword(base_addr + 0x12), jumpheight = memory.readword(base_addr + 0x0e),
["currentframe"] = memory.readword(base_addr + 0x18), style = memory.readword(base_addr + 0x12),
["nextframe"] = memory.readword(base_addr + 0x1a), currentframe = memory.readword(base_addr + 0x18),
["state"] = memory.readword(base_addr + 0x1e), nextframe = memory.readword(base_addr + 0x1a),
["velox"] = memory.readsword(base_addr + 0x20), state = memory.readword(base_addr + 0x1e),
["veloy"] = memory.readsword(base_addr + 0x24), velox = memory.readsword(base_addr + 0x20),
["velomaxx"] = memory.readsword(base_addr + 0x26), veloy = memory.readsword(base_addr + 0x24),
["velomaxy"] = memory.readsword(base_addr + 0x2a), velomaxx = memory.readsword(base_addr + 0x26),
["motion"] = memory.readword(base_addr + 0x2e), velomaxy = memory.readsword(base_addr + 0x2a),
["attr"] = memory.readword(base_addr + 0x30), motion = memory.readword(base_addr + 0x2e),
["animnum"] = memory.readword(base_addr + 0x36), attr = memory.readword(base_addr + 0x30),
["remainingframe"] = memory.readword(base_addr + 0x38), animnum = memory.readword(base_addr + 0x36),
["animcontrol"] = memory.readword(base_addr + 0x3a), remainingframe = memory.readword(base_addr + 0x38),
["animreadpos"] = memory.readword(base_addr + 0x3c), animcontrol = memory.readword(base_addr + 0x3a),
["animcontrol2"] = memory.readword(base_addr + 0x3e), animreadpos = memory.readword(base_addr + 0x3c),
["animformat"] = memory.readword(base_addr + 0x40), animcontrol2 = memory.readword(base_addr + 0x3e),
["damage1"] = memory.readword(base_addr + 0x44), animformat = memory.readword(base_addr + 0x40),
["damage2"] = memory.readword(base_addr + 0x46), damage1 = memory.readword(base_addr + 0x44),
["damage3"] = memory.readword(base_addr + 0x48), damage2 = memory.readword(base_addr + 0x46),
["damage4"] = memory.readword(base_addr + 0x4a), damage3 = memory.readword(base_addr + 0x48),
["damage5"] = memory.readword(base_addr + 0x4c), damage4 = memory.readword(base_addr + 0x4a),
["damage6"] = memory.readword(base_addr + 0x4e), damage5 = memory.readword(base_addr + 0x4c),
["spriteparam"] = memory.readword(base_addr + 0x58), damage6 = memory.readword(base_addr + 0x4e),
spriteparam = memory.readword(base_addr + 0x58),
} }
end end
function sprite_details(idx) function sprite_details(idx)
local base_addr = idx * 94 + 0x7e0e9e local base_addr = idx * 94 + SPRITE_BASE
local sprite = get_sprite(base_addr) local sprite = get_sprite(base_addr)
if sprite["control"] == 0 then if sprite.control == 0 then
gui.text(0, 0, "Sprite "..idx.." (Empty)", fgcolor, bgcolor) text(0, 0, "Sprite "..idx.." (Empty)")
incsprite = 0 incsprite = 0
locked = false locked = false
lockdata = nil lockdata = nil
@ -170,7 +173,7 @@ function sprite_details(idx)
end end
if incsprite ~= 0 then if incsprite ~= 0 then
memory.writeword(base_addr + 0x36, sprite["animnum"] + incsprite) memory.writeword(base_addr + 0x36, sprite.animnum + incsprite)
lockdata = nil lockdata = nil
incsprite = 0 incsprite = 0
@ -184,7 +187,7 @@ function sprite_details(idx)
memory.writeregion(base_addr, 94, lockdata) memory.writeregion(base_addr, 94, lockdata)
end end
gui.text(0, 0, "Sprite "..idx..(locked and " (Locked)" or "")..":\n\n"..table_to_string(sprite), fgcolor, bgcolor) text(0, 0, "Sprite "..idx..(locked and " (Locked)" or "")..":\n\n"..util.table_to_string(sprite))
end end
function on_paint (not_synth) function on_paint (not_synth)
@ -193,7 +196,7 @@ function on_paint (not_synth)
local guiWidth, guiHeight = gui.resolution() local guiWidth, guiHeight = gui.resolution()
if showhelp then if showhelp then
gui.text(0, 0, [[ text(0, 0, [[
Keyboard Help Keyboard Help
=============== ===============
@ -207,70 +210,156 @@ Sprite Details:
[6] Enable / Disable Pokemon mode (take screenshots of enemies) [6] Enable / Disable Pokemon mode (take screenshots of enemies)
[7] Enable / Disable float mode (fly with up/down) [7] Enable / Disable float mode (fly with up/down)
]], fgcolor, bgcolor) [8] Enable / Disable stage tile rulers
]])
return return
end end
gui.text(guiWidth - 75, 0, "Help [0]", fgcolor, bgcolor) local toggles = ""
local stats = ""
if pokemon then if pokemon then
stats = stats.."Pokemon: "..pokecount.."\n" toggles = toggles..string.format("Pokemon: %d\n", pokecount)
end end
if floatmode then if floatmode then
stats = stats.."Float on\n" toggles = toggles.."Float on\n"
end end
gui.text(0, guiHeight - 40, stats, fgcolor, bgcolor) text(0, guiHeight - 40, toggles)
stats = stats.."\nPokemon: "..pokecount local directions = {
"Standard",
"Blur",
"Up"
}
local cameraX = memory.readword(0x7e17ba) - 256 local cameraX = memory.readword(CAMERA_X) - 256
local cameraY = memory.readword(0x7e17c0) - 256 local cameraY = memory.readword(CAMERA_Y) - 256
local cameraDir = memory.readbyte(CAMERA_MODE)
local partyScreenX = (memory.readword(0x7e0a2a) - 256 - cameraX) * 2 local direction = directions[cameraDir+1]
local partyScreenY = (memory.readword(0x7e0a2c) - 256 - cameraY) * 2
if detailsidx ~= -1 then local vertical = memory.readword(TILE_COLLISION_MATH_POINTER) == VERTICAL_POINTER
sprite_details(detailsidx)
else
gui.text(0, 0, "[1] <- Sprite Details Off -> [2]", fgcolor, bgcolor)
end
gui.text(guiWidth - 200, guiHeight - 20, "Camera: "..tostring(cameraX)..","..tostring(cameraY), fgcolor, bgcolor) local stats = string.format([[
%s camera %d,%d
Vertical: %s
Tile offset: %04x
]], direction, cameraX, cameraY, vertical, party_tile_offset)
gui.text(partyScreenX, partyScreenY, "Party", fgcolor, bgcolor) text(guiWidth - 200, guiHeight - 60, stats)
local partyX = memory.readword(PARTY_X) - 256
local partyY = memory.readword(PARTY_Y) - 256
text((partyX - cameraX) * 2, (partyY - cameraY) * 2 + 20, "Party")
local sprites = {} local sprites = {}
for idx = 0,20,1 do for idx = 0,22,1 do
local base_addr = idx * 94 + 0x7e0e9e local base_addr = idx * 94 + SPRITE_BASE
local sprite = get_sprite(base_addr) local sprite = get_sprite(base_addr)
sprites[idx] = sprite sprites[idx] = sprite
if sprite["control"] == 0 then if sprite.control == 0 then
goto continue goto continue
end end
local spriteScreenX = (sprite["x"] - 256 - cameraX) * 2 local spriteScreenX = (sprite.x - 256 - cameraX) * 2
local spriteScreenY = (sprite["y"] - 256 - cameraY) * 2 local spriteScreenY = (sprite.y - 256 - cameraY) * 2
local sprcolor = bgcolor local sprcolor = BG_COLOR
if detailsidx == idx then if detailsidx == idx then
sprcolor = 0x00ff0000 sprcolor = 0x00ff0000
end end
gui.text(spriteScreenX, spriteScreenY, sprite["animnum"]..","..sprite["attr"], fgcolor, sprcolor) gui.text(spriteScreenX, spriteScreenY, sprite.control..","..sprite.animnum..","..sprite.attr, FG_COLOR, sprcolor)
local filename = os.getenv("HOME").."/neat-donk/catchem/"..sprite["animnum"]..","..sprite["attr"]..".png" local filename = os.getenv("HOME").."/neat-donk/catchem/"..sprite.animnum..","..sprite.attr..".png"
if pokemon and spriteScreenX > (guiWidth / 4) and spriteScreenX < (guiWidth / 4) * 3 and spriteScreenY > (guiHeight / 3) and spriteScreenY < guiHeight and not file_exists(filename) then if pokemon and spriteScreenX > (guiWidth / 4) and spriteScreenX < (guiWidth / 4) * 3 and spriteScreenY > (guiHeight / 3) and spriteScreenY < guiHeight and not util.file_exists(filename) then
gui.screenshot(filename) gui.screenshot(filename)
pokecount = pokecount + 1 pokecount = pokecount + 1
end end
::continue:: ::continue::
end end
if rulers and cameraX >= 0 then
local halfWidth = math.floor(guiWidth / 2)
local halfHeight = math.floor(guiHeight / 2)
local cameraTileX = math.floor(cameraX / TILE_SIZE)
gui.line(0, halfHeight, guiWidth, halfHeight, BG_COLOR)
for i = cameraTileX, cameraTileX + guiWidth / TILE_SIZE / 2,1 do
gui.text((i * TILE_SIZE - cameraX) * 2, halfHeight, tostring(i), FG_COLOR, BG_COLOR)
end
local cameraTileY = math.floor(cameraY / TILE_SIZE)
gui.line(halfWidth, 0, halfWidth, guiHeight, BG_COLOR)
for i = cameraTileY, cameraTileY + guiHeight / TILE_SIZE / 2,1 do
gui.text(halfWidth, (i * TILE_SIZE - cameraY) * 2, tostring(i), FG_COLOR, BG_COLOR)
end
end
local tilePtr = memory.readhword(TILEDATA_POINTER)
local solidLessThan = memory.readword(SOLID_LESS_THAN)
for x = -TILE_RADIUS, TILE_RADIUS, 1 do
for y = -TILE_RADIUS, TILE_RADIUS, 1 do
local offset = 0
if vertical then
offset = party_tile_offset + (y * 24 + x) * 2
else
offset = party_tile_offset + (x * 16 + y) * 2
end
local tile = memory.readword(tilePtr + offset)
if tile == 0 or tile >= solidLessThan then
goto continue
end
local tileX = (math.floor(partyX / TILE_SIZE + x) * TILE_SIZE - cameraX)
local tileY = (math.floor(party_y_ground / TILE_SIZE + y) * TILE_SIZE - cameraY)
gui.text(tileX * 2, tileY * 2, string.format("%04x,%02x", offset & 0xffff, tile), FG_COLOR, 0x66888800)
::continue::
end
end
if detailsidx ~= -1 then
sprite_details(detailsidx)
else
text(0, 20, "[1] <- Sprite Details Off -> [2]")
end
text(guiWidth - 125, 20, "Help [Hold 0]")
end
function tile_retrieval()
local tile = math.floor(memory.getregister("y") / 2) * 2
local newX = memory.readword(0x7e00a6)
local partyX = memory.readword(PARTY_X)
local oldX = partyX & 0x1f
local partyY = memory.readword(PARTY_Y)
if oldX - 5 < newX and newX < oldX + 5 and
not jumping and
memory.readword(0x7e0034) == partyY then
set_party_tile_offset(tile)
party_y_ground = partyY - 256
end
end
function on_timer()
local sec, usec = utime()
local now = sec * 1000000 + usec
if last_called + 100 * 1000 < now then
party_tile_offset = party_tile_offset_debounce
end
set_timer_timeout(100 * 1000)
end end
input.keyhook("1", true) input.keyhook("1", true)
@ -280,4 +369,9 @@ input.keyhook("4", true)
input.keyhook("5", true) input.keyhook("5", true)
input.keyhook("6", true) input.keyhook("6", true)
input.keyhook("7", true) input.keyhook("7", true)
input.keyhook("8", true)
input.keyhook("0", true) input.keyhook("0", true)
memory2.BUS:registerexec(TILE_RETRIEVAL, tile_retrieval)
set_timer_timeout(100 * 1000)

View file

@ -7,11 +7,11 @@ function _M.getPositions()
partyX = memory.readword(0x7e0a2a) - 256 partyX = memory.readword(0x7e0a2a) - 256
partyY = memory.readword(0x7e0a2c) - 256 partyY = memory.readword(0x7e0a2c) - 256
local cameraX = memory.readword(0x7e17ba) - 256 local cameraX = memory.readword(0x7e17ba) - 256
local cameraY = memory.readword(0x7e17c0) - 256 local cameraY = memory.readword(0x7e17c0) - 256
_M.screenX = partyX-layer1x _M.screenX = (partyX-cameraX)*2
_M.screenY = partyY-layer1y _M.screenY = (partyY-cameraY)*2
end end
function _M.getBananas() function _M.getBananas()
@ -99,10 +99,9 @@ function _M.getInputs()
local inputs = {} local inputs = {}
local inputDeltaDistance = {} local inputDeltaDistance = {}
local layer1x = memory.read_s16_le(0x1A); local layer1x = memory.readword(0x7f0000);
local layer1y = memory.read_s16_le(0x1C); local layer1y = memory.read_s16_le(0x1C);
for dy=-config.BoxRadius*16,config.BoxRadius*16,16 do for dy=-config.BoxRadius*16,config.BoxRadius*16,16 do
for dx=-config.BoxRadius*16,config.BoxRadius*16,16 do for dx=-config.BoxRadius*16,config.BoxRadius*16,16 do
inputs[#inputs+1] = 0 inputs[#inputs+1] = 0
@ -154,11 +153,9 @@ function _M.getInputs()
end end
function _M.clearJoypad() function _M.clearJoypad()
controller = {}
for b = 1,#config.ButtonNames do for b = 1,#config.ButtonNames do
controller["P1 " .. config.ButtonNames[b]] = false input.set(0, b - 1, 0)
end end
joypad.set(controller)
end end
return _M return _M

View file

@ -4,6 +4,7 @@ config = require "config"
spritelist = require "spritelist" spritelist = require "spritelist"
game = require "game" game = require "game"
mathFunctions = require "mathFunctions" mathFunctions = require "mathFunctions"
util = require "util"
Inputs = config.InputSize+1 Inputs = config.InputSize+1
Outputs = #config.ButtonNames Outputs = #config.ButtonNames
@ -640,7 +641,10 @@ function initializePool()
end end
function initializeRun() function initializeRun()
savestate.load(config.NeatConfig.Filename); print("Hello")
print(config.NeatConfig.Filename)
local rew = movie.to_rewind(config.NeatConfig.Filename)
movie.unsafe_rewind(rew)
if config.StartPowerup ~= NIL then if config.StartPowerup ~= NIL then
game.writePowerup(config.StartPowerup) game.writePowerup(config.StartPowerup)
end end
@ -686,7 +690,6 @@ if pool == nil then
initializePool() initializePool()
end end
function nextGenome() function nextGenome()
pool.currentGenome = pool.currentGenome + 1 pool.currentGenome = pool.currentGenome + 1
if pool.currentGenome > #pool.species[pool.currentSpecies].genomes then if pool.currentGenome > #pool.species[pool.currentSpecies].genomes then
@ -709,7 +712,6 @@ end
form = forms.newform(500, 500, "Mario-Neat") form = forms.newform(500, 500, "Mario-Neat")
netPicture = forms.pictureBox(form, 5, 250,470, 200) netPicture = forms.pictureBox(form, 5, 250,470, 200)
--int forms.pictureBox(int formhandle, [int? x = null], [int? y = null], [int? width = null], [int? height = null]) --int forms.pictureBox(int formhandle, [int? x = null], [int? y = null], [int? width = null], [int? height = null])
function displayGenome(genome) function displayGenome(genome)

BIN
pool/PiratePanic.lsmv Normal file

Binary file not shown.

44
util.lua Normal file
View file

@ -0,0 +1,44 @@
local _M = {}
function _M.table_to_string(tbl)
local result = "{"
local keys = {}
for k in pairs(tbl) do
table.insert(keys, k)
end
table.sort(keys)
for _, k in ipairs(keys) do
local v = tbl[k]
if type(v) == "number" and v == 0 then
goto continue
end
-- Check the key type (ignore any numerical keys - assume its an array)
if type(k) == "string" then
result = result.."[\""..k.."\"]".."="
end
-- Check the value type
if type(v) == "table" then
result = result..table_to_string(v)
elseif type(v) == "boolean" then
result = result..tostring(v)
else
result = result.."\""..v.."\""
end
result = result..",\n"
::continue::
end
-- Remove leading commas from the result
if result ~= "" then
result = result:sub(1, result:len()-1)
end
return result.."}"
end
function _M.file_exists(name)
local f=io.open(name,"r")
if f~=nil then io.close(f) return true else return false end
end
return _M