-- TAStudio Logger Framework
-- version 0.95, BizHawk 2.11
-- Not to be run in the Lua Console!
----------------
-- How to use --
----------------
-- With this script you can log values from RAM and display them as text and color in TAStudio.
-- It is also possible to compare values with values in branches or the previous playback and even offset the branch comparision by using markers.
-- In the status bar at the bottom it displays the branch you want to compare against, with an dropdown menu with a branch to select.
-- The number in the dropdown indicates how much faster that branch compared to the current inputs, a number lower than 0 means that branch is faster and vice versa.
-- No number means, the current section is not found in that branch.
-- Next to it are 3 buttons, two to change the selected branch index by 1
-- and the "Best" button which tries to find a branch with the shortest section for the section currently worked on.
-- Sections are markers, that don't start with "-" or have an empty text.
-- The section start is the first marker upwards from the current framecount, that fulfils these conditions.
-- The section end is the one downwards that fulfils these conditions.
-- Name your markers individually, e.g.: "Level 1 Map", "Level 2 Map" and not "Map get" for every level, as it messes up finding the section start and end.
-- Last is the name of the current section start and how much faster or slower that is compared to the branch you compare.
-- Lower than 0 means you are faster than the branch and vice versa.
-- The logs are saved and loaded as branches are saved and loaded, as well by closing and starting the script.
-- The files are in your movie folder. It won't save to a default.tasproj!
---------------------------
-- How to write a script --
---------------------------
-- 1st.: Write "require("TAStudioLogger")" in the top of your script.
-- Navigate to the folder where TAStudioLogger.lua sits with relative paths, if both script aren't in the same folder.
-- 2nd.: Add Loggers with e.g. "AddLogger("XVel", "s16_le@0xABCD")"
-- or "AddLogger("XPos", function () return memory.read_u8(0x1234) + memory.read_u8(0x1254)*16 end)"
-- or "AddLogger("Flags", 0xAABB)"
-- The Loggers take the form "AddLogger(<loggername>, <addressexpression>)"
-- 'loggername' is just a string. It's the name of the logger to be used in the AddPrinter function.
-- 'addressexpression' is either a function that returns a number,
-- a number of the adress (returns 1 byte unsigned only) or a string with shorthand expression: "<size>@<address>",
-- where 'size' is the size of the address (same as in memory.read_.. functions for Bizhawk) and 'address' is the address to log.
-- There are string shorthand form expressions for:
-- "<addr1>+<addr2>" for adding two values - value1+value2,
-- "<addr1>#<addr2>" for multiplying the first value by 256 and then add the second value - value1*256+value2,
-- "<addr1>+<addr2>#<addr3>" for adding the first two values, multiplying by 256, then add the third value - (value1+value2)*256+value3
-- 3rd.: Add Printers by writing for example: "AddPrinter("XVel", "XVel", "gradientabs,100", nil, 4)"
-- or "AddPrinter("P1 A", function(index) return GetLogValue("Flags", index) & 3 end, "#FFDD7040", "")"
-- or "AddPrinter("rDX", "relbranchcomp,XPos,XVel", "onchange;gradient,20,0xFFFF0000,0xFF00FF00;0xFFAAAAAA", "whenabove,0;modform", 4)"
-- Printers are written as follows: "AddPrinter(<columnname>, <valueexpression>[, <colorexpression>][, <textexpression>][, <digits>])"
-- 'columnname' is the name of the new column added to TAStudio by the script or an existing button column.
-- 'valueexpression' can be just a string of the name of one of your Loggers as 'loggername',
-- a function that takes 'index' as a parameter and returns a number.
-- For example: function(index) return GetLogValue("Flags", index) & 3 end
-- or a shorthand form string like "<shorthand>,<logname>[,<norm>]",
-- where 'shorthand' is the function. It can be of the following:
-- "diff" for 'Difference' function, difference to last frame, e.g.: "diff,XVel"
-- "branchval" for 'GetBranchLogValue' function, value from the log in the selected branch, e.g.: "branchval,XVel"
-- "branchdiff" for 'BranchDifference' function, difference to last frame in the selected branch log, e.g.: "branchdiff,XPos"
-- "branchcomp" for 'BranchCompare' function, subtracts the value in the branch log from the current log, e.g.: "branchcomp,XPos"
-- "relbranchcomp" for 'RelativeBranchCompare' function, does BranchCompare, but with marker offsets, if a marker with the same note is found in the branch, e.g.: "relbranchcomp,XPos"
-- "oldval" for 'GetOldLogValue' function, value from previous playback, the values also change from branch loading, e.g.: "oldval,XVel"
-- "olddiff" for 'OldLogDifference' function, difference to last value from the previous playback, e.g.: "olddiff,XPos"
-- "oldcomp" for 'OldLogCompare' function, subtracts the value in the old log from the current log, e.g.: "oldcomp,XPos"
-- 'name' is the name of one of your logs, so is the optional parameter 'norm'.
-- You can normalize - multiply the return value of above functions by the sign of 'norm' - by adding a 3rd parameter, e.g.: "branchcomp,XPos,XVel"
-- 'colorexpression' can be an number 0xAARRGGBB, a string "#[AA]RRGGBB" expressing a color value or from the predefined Colors table,
-- a table with log values as the key and a color as the value, e.g.: {0xFFE0E0E0, 0xFFE04090, 0xFFFF1050},
-- a function that takes 'value', 'index' as parameters and returns a color, e.g.: function(value, index) if value == 15 then return Color(index, , 0xFF8080FF ) end end
-- or the shorthand form string of the ColorGradient function, "gradient,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
-- for example "gradient,20,0xFFFF0000,0xFF00FF00,1.15,5", use "gradientabs" for absolute values,
-- a string in the form of "table,<tablename>" e.g.: "table,mycolortable".
-- Additionally another parameter can be passed in the shorthand form string to decide which color to use when the returned value is nil, e.g.: "gradient,50;0xFFFFEEEE"
-- 'textexpression' is optional, if left out it displays the value of the 'valueexpression',
-- a string that is printed, useful with the conditional expression further down,
-- a table with log values as the key and a color as the value, e.g.: "{"stand", "walk", "jump"}",
-- a function that takes 'value' as a parameter and 'index' optionally and returns a string "function(value) return value < 15 and "invincible" or "" end,
-- the string "modform" to display the log values as <value>/256..":"..<value>%256; prints '125893' as '491:197', "modformh" to display it as hexadecimal,
-- "modformdh" to display only the remainder as hexadecimal, those functions have an optional parameter for the divisor, e.g.: "modformh,16"
-- the string "hex" to display the value as hexadecimal, with an optional parameter for the size "hex,4".
-- a string in the form of "table,<tablename>" e.g.: "table,mytexttable". The table must be global.
-- or pass in 'false' as a boolean to display no text.
-- Text wont be displayed in button columns.
-- Furthermore both 'colorexpression' and 'textexpression' as a shorthand form string can have an additional expression at the beginning to have a condition when to print text or color the cell.
-- Options for that are:
-- "onchange", "when", "whennot", "whenabove" and "whenbelow", the "when.." conditions have an optional parameter for a number, if empty it defaults to 0. E.g.: "whenabove,16".
-- When one of those expression used alone for the textexpression shorthand form, it will display the value from valueexpression under the given condition.
-- Examples:
-- "when,1;0xFF8040FF;0xFFFFFFFF" colors the cell when the value is 1 otherwise the cell is colored white
-- "whenabove;dmg" prints "dmg" in the cell when the value is above 0
-- 4th.: Add "InitializeLogger()" after setting up all your Loggers and Printers.
-- 5th.: In the default while true loop at the bottom write "UpdateLogger()" and replace "emu.frameadvance()" with "emu.yield()" if necessary.
--------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------
-- Logger --
--------------
local Logs = { }
local Printers = { }
local branches = { } -- entries with 'values = {}'
local oldLogs = { } -- Contains the values from Logs from the previous playback
local currentmarker = nil -- Marker above or at playback coursor
local currentsection = nil -- First marker upwards, that doesn't start with "-" or hasn't an empty note
local lastmarker = nil -- For getting when marker changes
local markerdiff = 0 -- Offset from currentsection marker to the marker in selectedbranch with the same name
local selectedbranch = 1
local ungreenframe = 0 -- For drawing colors in TAStudio pale
local open = false -- For branch dropdown in status bar
local mousedown = false
local loaderror = false -- Prevents overriding log in case of loading wrong script
local lasteditedframe
local tastudio = tastudio
local memory = memory
local gui = gui
local event = event
local math = math
-- For writing addressexpression as shorthand form
local function AddressExpressionLookup(expr)
if string.match(expr, "^float_[bl]e@0x%x+$") == expr
then local s = bizstring.split(expr, "@")
local addr = tonumber(s[2])
if s[1] == "float_le" then return function () return memory.readfloat(addr) end
elseif s[1] == "float_be" then return function () return memory.readfloat(addr, true) end
else console.log("WARNING: Invalid shorthand addressexpression form "..expr)
return
end
else local readfuncs = {}
local n = 0
local readfuncpattern = "[us][813][62]?_?[bl]?e?@0x%x+"
for w in string.gmatch(expr, readfuncpattern) do
local s = bizstring.split(w, "@")
local addr = tonumber(s[2])
n = n + 1
if s[1] == "u8" then table.insert(readfuncs, function () return memory.read_u8(addr) end)
elseif s[1] == "s8" then table.insert(readfuncs, function () return memory.read_s8(addr) end)
elseif s[1] == "u16_le" then table.insert(readfuncs, function () return memory.read_u16_le(addr) end)
elseif s[1] == "u16_be" then table.insert(readfuncs, function () return memory.read_u16_be(addr) end)
elseif s[1] == "s16_le" then table.insert(readfuncs, function () return memory.read_s16_le(addr) end)
elseif s[1] == "s16_be" then table.insert(readfuncs, function () return memory.read_s16_be(addr) end)
elseif s[1] == "u32_le" then table.insert(readfuncs, function () return memory.read_u32_le(addr) end)
elseif s[1] == "u32_be" then table.insert(readfuncs, function () return memory.read_u32_be(addr) end)
elseif s[1] == "s32_le" then table.insert(readfuncs, function () return memory.read_s32_le(addr) end)
elseif s[1] == "s32_be" then table.insert(readfuncs, function () return memory.read_s32_be(addr) end)
end
end
if #readfuncs ~= n -- Atleast one function shorthand has a misspelling, like "u36_le"
then console.log("WARNING: Invalid shorthand addressexpression form "..expr)
return
end
if string.match(expr, "^"..readfuncpattern.."+"..readfuncpattern.."#"..readfuncpattern.."$") == expr
then return function() return (readfuncs[1]() + readfuncs[2]()) * 256 + readfuncs[3]() end -- (1st + 2nd)*256 + 3rd
elseif string.match(expr, "^"..readfuncpattern.."+"..readfuncpattern.."$") == expr
then return function() return readfuncs[1]() + readfuncs[2]() end -- 1st + 2nd
elseif string.match(expr, "^"..readfuncpattern.."#"..readfuncpattern.."$") == expr
then return function() return readfuncs[1]()*256 + readfuncs[2]() end -- 1st*256 + 2nd
elseif string.match(expr, "^"..readfuncpattern.."$") == expr
then return function() return readfuncs[1]() end -- 1st
else console.log("WARNING: Invalid shorthand addressexpression form "..expr)
return
end
end
end
--[[Adds a logger to the Logs table
Addressexpression needs to be a number, a string or a function which takes no argument and returns a number.
The value from that expression will be stored in the Logs[name].values for each frame.]]
function AddLogger(name, addressexpression)
if Logs[name] ~= nil
then console.log("WARNING: "..name.." is already logged.")
return
end
if type(addressexpression) == "number"
then local expr = addressexpression
addressexpression = function () return memory.read_u8(expr) end
elseif type(addressexpression) == "string"
then local expr = AddressExpressionLookup(addressexpression)
if expr == nil
then return
else addressexpression = expr
end
elseif type(addressexpression) ~= "function"
then console.log("Address needs to be a number, a string or a function.")
end
Logs[name] = {expression = addressexpression,
values = {}}
oldLogs[name] = {values = {}}
end
-- For writing valueexpression string shorthand form
local function ValueExpressionLookup(expr)
local params = bizstring.split(expr, ",")
local func
if params[1] == "branchcomp" then func = function(index) return BranchCompare(params[2], index) end
elseif params[1] == "relbranchcomp" then func = function(index) return RelativeBranchCompare(params[2], index) end
elseif params[1] == "diff" then func = function(index) return Difference(params[2], index) end
elseif params[1] == "branchval" then func = function(index) return GetBranchLogValue(params[2], index) end
elseif params[1] == "branchdiff" then func = function(index) return BranchDifference(params[2], index) end
elseif params[1] == "oldval" then func = function(index) return GetOldLogValue(params[2], index) end
elseif params[1] == "oldcomp" then func = function(index) return OldLogCompare(params[2], index) end
elseif params[1] == "olddiff" then func = function(index) return OldLogDifference(params[2], index) end
end
if func == nil
then return
end
if Logs[params[2]] == nil
then console.log("WARNING: "..params[2].." is not defined as a log in "..expr)
return
end
if params[3] ~= nil
then if Logs[params[3]] == nil
then console.log("WARNING: "..params[3].."is not defined as a log in "..expr)
return
end
return function(index) return Normalize(func(index), params[3], index) end
else return func
end
end
local function GetColorFromString(s)
if type(s) ~= "string"
then return
end
if string.match(s, "^#%x%x%x%x%x%x$") -- "#RRGGBB" RGB value
then return tonumber("0xFF"..s)
elseif string.match(s, "^#%x%x%x%x%x%x%x%x$") -- "#AARRGGBB" ARGB color value
then return tonumber("0x"..s)
elseif string.match(s, "^0x%x%x%x%x%x%x%x%x$") -- "0xAARRGGBB"
then return tonumber(s)
elseif string.match(s, "^Color%.%l+$") -- Colors table
then return Colors[string.match(s, "^Color%.(%l+)$")]
end
end
local function ConditionalExpressionLookup(expr, valexpr)
local params = bizstring.split(expr, ",")
local param = tonumber(params[2])
if params[1] == "onchange" then return function(value, index) return PrintOnChange(valexpr, index) end
elseif params[1] == "whennot" then return function(value, index) return value ~= (param or 0) and true or false end
elseif params[1] == "when" then return function(value, index) return value == (param or 0) and true or false end
elseif params[1] == "whenabove" then return function(value, index) return value > (param or 0) and true or false end
elseif params[1] == "whenbelow" then return function(value, index) return value < (param or 0) and true or false end
end
end
local function ColorExpressionLookup(expr, valexpr)
local funcs = bizstring.split(expr, ";")
local k
local when -- to color
local how -- to color
when = ConditionalExpressionLookup(funcs[1], valexpr)
if when == nil
then k = 1 -- no when function, how function must be first in string
else k = 2 -- when function exists at index 1, how function comes after it
end
if funcs[k] == nil
then console.log("WARNING: No shorthandform for colorexpression function provided for "..expr..".")
return
end
if GetColorFromString(funcs[k])
then local color = GetColorFromString(funcs[k])
how = function(value, index) return Color(index, color) end
elseif bizstring.startswith(funcs[k], "gradient,") -- ColorGradient function shorthand form
then local params = bizstring.split(funcs[k], ",") -- "gradient,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
local startcolor = GetColorFromString(params[3])
local endcolor = GetColorFromString(params[4])
how = function(value, index) return ColorGradient(value, index, tonumber(params[2]), startcolor, endcolor, tonumber(params[5]), tonumber(params[6])) end
elseif bizstring.startswith(funcs[k], "gradientabs,") -- ColorGradient function with absolute value shorthand form
then local params = bizstring.split(funcs[k], ",") -- "gradientabs,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
local startcolor = GetColorFromString(params[3])
local endcolor = GetColorFromString(params[4])
how = function(value, index) return ColorGradient(math.abs(value), index, tonumber(params[2]), startcolor, endcolor, tonumber(params[5]), tonumber(params[6])) end
elseif bizstring.startswith(funcs[k], "table,")
then local colortable = _ENV[string.match(funcs[k], "^table,([%w_]+)$")]
if colortable == nil
then console.log("WARNING: Table with the name "..tostring(string.match(funcs[k], "^table,([%w_]+)$")).." does not exist or is local.")
return
end
how = function(value, index) return MultiColor(value, index, colortable) end
else console.log("WARNING: Third parameter colorexpression as a string must be \"#RRGGBB\" or \"#AARRGGBB\". Or shorthandform for gradient or table function.\nCurrently: "..expr)
return
end
if how == nil and when == nil
then return
end
local defaultcolor
if when ~= nil and funcs[k+1] ~= nil
then defaultcolor = GetColorFromString(funcs[k+1])
end
return function(value, index)
if when ~= nil
then if when(value, index) == true
then return how(value, index)
else return defaultcolor
end
else return how(value, index)
end
end
end
-- Return the sign of the value x
local function sign(x)
return x < 0 and -1 or x >= 0 and 1
end
local function signOrZero(x)
return x < 0 and -1 or x > 0 and 1 or 0
end
local function TextExpressionLookup(expr, valexpr)
local funcs = bizstring.split(expr, ";")
local k
local when -- to print
local how -- to print
when = ConditionalExpressionLookup(funcs[1], valexpr)
if when == nil
then k = 1 -- no 'when' function, 'how' function must be first in string
else k = 2 -- 'when' function exists at index 1, 'how' function comes after it
end
local params = bizstring.split(funcs[k], ",")
local param = tonumber(params[2])
--TODO:index for how functions here not needed
-- Display text as '<divisor>:<remainder>', optional parameter for divisor
if params[1] == "modform" -- More understandable to prefix it with the sign and use the absolute values and not calculate the remainder as is
then how = function(value, index) return tostring((sign(value) == -1 and "-" or "")..(math.abs(value)//(param or 256))..":"..math.abs(value)%(param or 256)) end
-- Display text as '<divisor>:<remainder>' as hexadecimal, optional parameter for divisor
elseif params[1] == "modformh"
then how = function(value, index) return tostring(sign(value) == -1 and "-" or ""..string.format("%X:%X",math.abs(value)//(param or 256), math.abs(value)%(param or 256))) end
-- Display text as '<divisor>:<remainder>' with divisor as decimal and remainder as hex, optional parameter for divisor
elseif params[1] == "modformdh"
then how = function(value, index) return tostring(sign(value) == -1 and "-" or ""..(math.abs(value)//(param or 256))..":"..string.format("%X", math.abs(value)%(param or 256))) end
-- Display text as hexadecimal, optional parameter for number of digits
elseif params[1] == "hex"
then how = function(value, index) return string.format("%X", value):sub(param and -param or 0) end
-- Display text from table
elseif bizstring.startswith(funcs[k], "table,")
then local texttable = _ENV[string.match(funcs[k], "^table,([%w_]+)$")]
if texttable == nil
then console.log("WARNING: Table with the name "..tostring(string.match(funcs[k], "^table,([%w_]+)$")).." does not exist or is local.")
return
end
how = function(value, index) return texttable[value] or "" end
elseif funcs[k] and string.len(funcs[k]) > 0-- and how == nil and (when == nil or k==2)--(when==nil and how==nil or how==nil and k==2)
then how = function(value, index) return tostring(funcs[k]) end -- Output the text, with or without a condition
end
if how == nil and when == nil
then return -- Output nothing for empty strings
end
return function(value, index)
if when ~= nil
then if when(value, index) == true
then if how ~= nil
then return how(value, index)
else return tostring(value)
end
else return ""
end
elseif how ~= nil
then return how(value, index)
else return tostring(value)
end
end
end
-- Checks wheter the column belongs to the input button columns
local function IsButtonColumn(column)
for button in pairs(joypad.getimmediate()) do
if button == column
then return true
end
end
return false
end
--[[Adds a printer to the Printers table for outputing text and color into the TAStudio list.
Sets predefined function depending on the parameters passed.
Or the printer uses functions passed as parameters.
Columnname is a string for TAStudio button list header.
valueexpression needs to be a string or a function.
As a string it needs to be the name for a Logs table, or a shorthand function.
As a function it needs to be a function that takes a number (index) as an argument and returns a number.
colorexpression will define which colors will be drawn into the TAStudio list.
It can be a a string with "#RRGGBB" or "#AARRGGBB" (A)RGB values, or a shorthandfunction.
A number in the form of 0xAARRGGBB ARGB hexadecimal color value.
A table with value/color pairs in which each value can be assigned a color.
A function which takes two numbers (value, index) as an argument and returns a color.
As nil or no parameter passed as a parameter for colorexpression, no color will be drawn.
textexpression will define how text will be displayed in the TAStudio list.
No text can be printed into button columns.
When nil or no parameter passed the value for each frame will be displayed.
It can be a string which will be displayed if the value for the corresponding frame is greater than zero. "" to display no text, or a shorthandfunction.
A table with value/text pairs in which each value can be a assigned a text.
False for which no text will be displayed.
A function which takes a number (value) and a number (index) as an optional paramenter as arguments and returns a string.
Digits is the number of digits to be displayed as text in TAStudio list, default is 3.]]
function AddPrinter(columnname, valueexpression, colorexpression, textexpression, digits)
if Printers[columnname] ~= nil
then console.log(columnname.." is already printed")
return
end
-- valueexpression parameter check
if type(valueexpression) == "string"
then local expr = ValueExpressionLookup(valueexpression)
if expr ~= nil
then valueexpression = expr
else if Logs[valueexpression] == nil
then console.log("WARNING: Invalid string valueexpression: "..tostring(valueexpression).."\n Must be name of a Log or shorthandform function.")
return
else local s = valueexpression
valueexpression = function(index) return Logs[s].values[index] end -- Value will be the value in the Log as is with the name of the string.
end
end
elseif type(valueexpression) ~= "function" -- Value will be the number returned from the function
then console.log("WARNING: Second parameter valueexpression ("..tostring(valueexpression)..") must be a string or a function.")
return
end
-- colorexpression parameter check
if type(colorexpression) == "table"
then local colortable = colorexpression
colorexpression = function(value, index) return MultiColor(value, index, colortable) end
elseif type(colorexpression) == "number" -- 0xAARRGGBB ARGB value
then local color = colorexpression
colorexpression = function(value, index) if value > 0 then return Color(index, color) end end
elseif type(colorexpression) == "string"
then local expr = ColorExpressionLookup(colorexpression, valueexpression)
if expr ~= nil
then colorexpression = expr
else colorexpression = nil
end
elseif type(colorexpression) ~= "nil" and type(colorexpression) ~= "function"
then console.log("WARNING: Third parameter colorexpression must be a number, a string, a table, a function or nil.")
return
end
-- textexpression parameter check
if type(textexpression) == "nil"
then textexpression = function(value) return tostring(value) end -- Display the value from the valueexpression as text
elseif type(textexpression) == "string"
then local expr = TextExpressionLookup(textexpression, valueexpression)
if expr ~= nil
then textexpression = expr
else textexpression = nil
end
elseif type(textexpression) == "table" -- Value, Text pairs for displaying text with the corresponding value
then local t = textexpression
if next(t) ~= nil
then textexpression = function(value) return t[value] or "" end
else textexpression = nil
end
elseif type(textexpression) == "boolean"
then if textexpression == true
then textexpression = function(value) return tostring(value) end -- Display the value from the valueexpression as text
else textexpression = nil -- Display no text
end
elseif type(textexpression) ~= "function"
then console.log("WARNING: Fourth parameter textexpression must be a string, a table, a function, boolean or nil.")
end
------------------------------
if IsButtonColumn(columnname)
then if textexpression ~= nil
then console.log("WARNING: Can't print values into column "..columnname..".Only color will be displayed.")
end
textexpression = nil -- Don't print text in button columns
else digits = digits or 3
tastudio.addcolumn(columnname, columnname, (digits * 6) + 14)
end
Printers[columnname] = {value = valueexpression,
color = colorexpression,
text = textexpression}
end
------------
-- File --
------------
-- Saves the log file to disk.
local function SaveFile(index, source)
local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj")
if index >= 0
then filename = filename.."_"..tostring(index+1)..".log"--..tastudio.getbranches()[index].Id..".log"
elseif index == -1
then filename = filename.."_backup.log"
elseif index == -2
then filename = filename.."_current.log"
elseif index == -3
then filename = filename.."_old.log"
else console.log("Invalid file save index")
return
end
if string.match(movie.filename(), "default.tasproj") == "default.tasproj"
then console.log("WARNING: The movie has yet not been saved. The log file wasn't saved.")
return
elseif loaderror == true
then console.log("The file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").." wasn't saved as a load error occured and the log files may contain data from another script.\nDelete or move the files from the current movie folder if you want to start a new log and restart the script")
return
end
local file = io.open(filename, "w+")
source = source or Logs
file:write("ungreenframe:\n"..tostring(ungreenframe).."\n")
for k in pairs(source) do -- Save header with log order
file:write(tostring(k).."|")
end
file:write("\n")
for i = 0, movie.length(), 1 do -- Save log values to file
for k, v in pairs(source) do
file:write(tostring(source[k].values[i]).."|")
end
file:write("\n")
end
file:close()
gui.addmessage("Saved log file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)"))
end
-- Loads the log file from disk
local function LoadFile(index, target)
--local filename = string.match(movie.filename(), "[%w%p%s\\]+\\([%w%p%s]+)%.tasproj")
local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj")
if index >= 0
then filename = filename.."_"..tostring(index+1)..".log"--tastudio.getbranches()[index].Id..".log"
elseif index == -1
then filename = filename.."_backup.log"
elseif index == -2
then filename = filename.."_current.log"
elseif index == -3
then filename = filename.."_old.log"
else console.log("Error: Invalid file load index")
return
end
local file = io.open(filename, "r")
if file ~= nil
then file:read("l")
ungreenframe = tonumber(file:read("l"))
local names = bizstring.split(file:read("l"), "|")
for k,v in pairs(names) do
if Logs[v] == nil
then file:close() -- Logs in the file don't match the defined logs in the script
console.log("Error reading file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").."\n"..v.." is not defined as a log.\nYou may have opened the wrong movie or script.")
loaderror = true
return
end
if target[v] == nil
then target[v] = {values = {}}
end
end
local i = 0
for line in file:lines("l") do -- Load log values from the file
local s = bizstring.split(line, "|")
for k,v in pairs(names) do
target[v].values[i] = tonumber(s[k])
end
i = i + 1
end
file:close()
gui.addmessage("Loaded log file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)"))
--else console.log("File "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").." was not found,")
end
end
----------------
-- TAStudio --
----------------
-- Responsible for printing text into the TAStudio list
local function TAStudioText(index, column)
if Printers[column] ~= nil
then if Printers[column].text ~= nil and Printers[column].value(index) ~= nil
then return Printers[column].text(Printers[column].value(index), index)
elseif IsButtonColumn(column) == false -- Don't make input invisible for button columns
then return ""
end
end
end
-- Responsible for coloring cells in the TAStudio list
local function TAStudioColor(index, column)
if Printers[column] ~= nil and Printers[column].color ~= nil and Printers[column].value(index) ~= nil
then return Printers[column].color(Printers[column].value(index), index) or nil
end
return nil
end
-- Called when the greenzone in invalidated by editing the movie
local function Ungreen(index)
-- This may not be the desired behaviour when considering the old log should only change on invalid greenzone
-- Copy to oldLogs when the input was edited after it was edited on an earlier frame and the user player over that section
-- if lasteditedframe and lasteditedframe < index and lasteditedframe < ungreenframe
-- then for k,v in pairs(Logs) do
-- for i = lasteditedframe, index-1, 1 do
-- oldLogs[k].values[i] = Logs[k].values[i]
-- end
-- end
-- end
lasteditedframe = index
if ungreenframe > index
then ungreenframe = index-- - 1
end
end
-------------
-- Color --
-------------
Colors = {}
Colors.white = 0xFFFFFFFF
Colors.gray = 0xFFFFFFFF
Colors.red = 0xFFFF4D4D
Colors.green = 0xFF4DFF4D
Colors.blue = 0xFF4D4DFF
Colors.yellow = 0xFFFFFF4D
Colors.magenta =0xFFFF00FF
Colors.cyan = 0xFF4DFFFF
Colors.orange = 0xFFFFA64D
Colors.purple = 0xFFD047FF
local function CalcPaleColor(alpha, red, green, blue)
red = math.floor((255-red)/120*(200-240)+255)
green = math.floor((255-green)/120*(200-240)+255)
blue = math.floor((255-blue)/120*(200-240)+255)
return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue
end
local function CalcPaleColor2(color)
local alpha = (color & 0xFF000000)>>24
local red = (color & 0x00FF0000)>>16
local green = (color & 0x0000FF00)>>8
local blue = (color & 0x000000FF)
red = math.floor((255-red)/120*(200-240)+255)
green = math.floor((255-green)/120*(200-240)+255)
blue = math.floor((255-blue)/120*(200-240)+255)
return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue
end
function Color(index, color)
if index < ungreenframe
then return color
else return CalcPaleColor2(color)
end
end
-- Each value in the table can be assigned a color
function MultiColor(value, index, colortable)
if colortable[value] ~= nil
then if index < ungreenframe
then return colortable[value]
else return CalcPaleColor2(colortable[value])
end
end
end
-- Returns the value or minimum if value < minimum or maximum if value > maximum
-- local function Clamp(value, minimum, maximum)
-- return math.max(minimum, math.min(value, maximum))
-- end
-- Optimized version of above
local function Clamp(value, minimum, maximum)
return value < maximum and value > minimum and value or value >= maximum and maximum or value <= minimum and minimum
end
-- The gradient starts at startcolor for values below minvalue and ends at endcolor for value greater than maxvalue.
-- When no paremeters for startcolor and endcolor are passed into the function, then the gradient will be from red to green.
-- The default value for minvalue is 0.
-- An optional parameter called exponent controls the distribution of the gradient.
function ColorGradient(value, index, maxvalue, startcolor, endcolor, minvalue, exponent)
minvalue = minvalue or 0
-- fraction is a value from 0 to 1, with minvalue being eqaul to 0 and maxvalue equal to 1
local fraction = ( Clamp(value-minvalue, 0, maxvalue-minvalue)/(maxvalue - minvalue) )^(exponent or 1)
startcolor = startcolor or 0xFFFF0000 -- default red
local as = (startcolor & 0xFF000000)>>24
local rs = (startcolor & 0x00FF0000)>>16
local gs = (startcolor & 0x0000FF00)>>8
local bs = (startcolor & 0x000000FF)
endcolor = endcolor or 0xFF00FF00 -- default green
local ae = (endcolor & 0xFF000000)>>24
local re = (endcolor & 0x00FF0000)>>16
local ge = (endcolor & 0x0000FF00)>>8
local be = (endcolor & 0x000000FF)
local alpha
local red
local green
local blue
if re - rs <= 32 and ge - gs <= 32 and be - bs <= 32 -- only descending slopes + tolerance
or re - rs >= -32 and ge - gs >= -32 and be - bs >= -32 -- only ascending slopes + tolerance
then -- Clamps the color value to the slope function y = (_e - _s)*x + _s
-- between _s and _e, where _s is the startcolor a,r,g,b part and _e is the endcolor part.
alpha = math.floor(Clamp((ae-as)*fraction + as, math.min(as,ae), math.max(as, ae)))
red = math.floor(Clamp((re-rs)*fraction + rs, math.min(rs,re), math.max(rs, re)))
green = math.floor(Clamp((ge-gs)*fraction + gs, math.min(gs,ge), math.max(gs, ge)))
blue = math.floor(Clamp((be-bs)*fraction + bs, math.min(bs,be), math.max(bs, be)))
else -- Clamps the color value to a slope function y = 2*(_e - _s)*x +_s for ascending slopes,
-- or y = 2*(_e - _s)*x + 2*_s-_e for descending slopes between _s and _e.
-- This makes ascending slope functions occupy fraction value between 0 and 0.5
-- and descending ones between 0.5 and 1. Scaled on the x-axis by half and shifted by 0.5 for descending slopes.
alpha = math.floor(Clamp((ae-as)*2*fraction + ((ae-as)<0 and 2*as-ae or as), math.min(as,ae), math.max(as, ae)))
red = math.floor(Clamp((re-rs)*2*fraction + ((re-rs)<0 and 2*rs-re or rs), math.min(rs,re), math.max(rs, re)))
green = math.floor(Clamp((ge-gs)*2*fraction + ((ge-gs)<0 and 2*gs-ge or gs), math.min(gs,ge), math.max(gs, ge)))
blue = math.floor(Clamp((be-bs)*2*fraction + ((be-bs)<0 and 2*bs-be or bs), math.min(bs,be), math.max(bs, be)))
end
if index >= ungreenframe
then return CalcPaleColor(alpha, red, green, blue)
end
return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue
end
---------------
-- Utility --
---------------
-- Checks wheter the value is contained in the values table
-- UNUSED
-- function AnyValueMatching(value, values)
-- for k,v in pairs(values) do
-- if value == v
-- then return true
-- end
-- end
-- return false
-- end
local function BranchCount()
if tastudio.getbranches()[0] == nil
then return 0
else return #tastudio.getbranches() + 1
end
end
-- Calculates the difference between current log value with the corresponding name and the value in the branch log for the selected branch
-- function BranchCompare(name, index)
-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
-- then return Logs[name].values[index] - branches[selectedbranch][name].values[index]
-- end
-- end
function BranchCompare(name, index)
local branch = branches[selectedbranch]
if branch -- Need to check if log for branch exists, the user may load a .tasproj containing branches without logs
then local branchvalue = branch[name].values[index]
local logvalue = Logs[name].values[index]
return logvalue and branchvalue and logvalue - branchvalue
end
end
-- UNUSED/OUTDATED:Similiar to BranchCompare, but multiplies the result by the sign of the value in the norm log
-- function BranchCompareNormalized(name, norm, index)
-- -- Assume when Logs[name].value[index] not nil then Logs[norm].value[index] must also not be nil
-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
-- then return (Logs[name].values[index] - branches[selectedbranch][name].values[index])*sign(Logs[norm].values[index])
-- end
-- end
-- Same as BranchCompare, but adjust for the difference for the current marker and uses an offset for the branch values index
-- function RelativeBranchCompare(name, index)
-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index-markerdiff] ~= nil
-- then return Logs[name].values[index] - branches[selectedbranch][name].values[index-markerdiff]
-- end
-- end
function RelativeBranchCompare(name, index)
local branch = branches[selectedbranch]
if branch -- Need to check if log for branch exists, the user may load a .tasproj containing branches without logs
then local branchvalue = branch[name].values[index-markerdiff]
local logvalue = Logs[name].values[index]
return logvalue and branchvalue and logvalue - branchvalue
end
end
-- UNUSED/OUTDATED:
-- function RelativeBranchCompareNormalized(name, norm, index)
-- -- Assume when Logs[name].value[index] not nil then Logs[norm].value[index] must also not be nil
-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index-markerdiff] ~= nil
-- then return (Logs[name].values[index] - branches[selectedbranch][name].values[index-markerdiff])*sign(Logs[norm].values[index])
-- end
-- end
-- Difference from previous frame
-- function Difference(name, index)
-- if Logs[name].values[index] ~= nil and Logs[name].values[index-1] ~= nil
-- then return Logs[name].values[index] - Logs[name].values[index-1]
-- end
-- end
function Difference(name, index)
local logvalue1 = Logs[name].values[index]
local logvalue2 = Logs[name].values[index-1]
return logvalue1 and logvalue2 and logvalue1 - logvalue2
end
-- Multiplies 'value' by the sign of 'norm'
-- function Normalize(value, norm, index)
-- if value ~= nil
-- then return value * sign(Logs[norm].values[index])
-- end
-- end
function Normalize(value, norm, index)
return value and value * sign(Logs[norm].values[index])
end
-- function GetLogValue(name, index)
-- if Logs[name].values[index] ~= nil
-- then return Logs[name].values[index]
-- end
-- end
function GetLogValue(name, index)
return Logs[name].values[index]
end
-- function GetBranchLogValue(name, index)
-- if branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
-- then return branches[selectedbranch][name].values[index]
-- end
-- end
function GetBranchLogValue(name, index)
local branch = branches[selectedbranch]
return branch and branch[name].values[index]
end
-- Difference from previous frame for branch log values
-- function BranchDifference(name, index)
-- if branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
-- and branches[selectedbranch][name].values[index-1] ~= nil
-- then return branches[selectedbranch][name].values[index] - branches[selectedbranch][name].values[index-1]
-- end
-- end
function BranchDifference(name, index)
local branch = branches[selectedbranch]
if branch
then local branchvalue1 = branch[name].values[index]
local branchvalue2 = branch[name].values[index-1]
return branchvalue1 and branchvalue2 and branchvalue1 - branchvalue2
end
end
-- function GetOldLogsValue(name, index)
-- if oldLogs[name] ~= nil and oldLogs[name].values[index] ~= nil
-- then return oldLogs[name].values[index]
-- end
-- end
function GetOldLogValue(name, index)
return oldLogs[name].values[index]
end
-- function OldLogsCompare(name, index)
-- if Logs[name].values[index] ~= nil and oldLogs[name] ~= nil and oldLogs[name].values[index] ~= nil
-- then return Logs[name].values[index] - oldLogs[name].values[index]
-- end
-- end
function OldLogCompare(name, index)
local logvalue = Logs[name].values[index]
local oldvalue = oldLogs[name].values[index]
return logvalue and oldvalue and logvalue - oldvalue
end
function OldLogDifference(name, index)
local oldvalue1 = oldLogs[name].values[index]
local oldvalue2 = oldLogs[name].values[index-1]
return oldvalue1 and oldvalue2 and oldvalue1 - oldvalue2
end
-- With this function, text or colors can be drawn into TAStudio when the value has changed from the previous one.
-- This function doesn't use 'name' as a parameter since it should decide wheter the value from 'valueexpression' has changed and not the values from a Log.
-- function PrintOnChange(valueexpression, index)
-- if valueexpression(index) ~= nil and valueexpression(index-1) ~= nil
-- then return (valueexpression(index) - valueexpression(index-1)) ~= 0 and true or false
-- end
-- return true
-- end
function PrintOnChange(valueexpression, index)
local value1 = valueexpression(index)
local value2 = valueexpression(index-1)
return value1 and value2 and value1 ~= value2 and true or false
end
-----------------
-- Status Bar ---
-----------------
-- Subtracts the frame number for the marker in the selected branch with the same note as the marker passed as the parameter from the parameter marker frame
local function CaclulateDifferenceToBranchMarker(markerframe)
if BranchCount() == 0
then return
end
local text = tastudio.getmarker(markerframe)
for k, v in pairs(tastudio.get_frames_with_markers(tastudio.getbranches()[selectedbranch-1].Id)) do
if text == tastudio.getmarker(v, tastudio.getbranches()[selectedbranch-1].Id)
then markerdiff = markerframe - v
break
end
end
end
-- Finds the first marker begining from start frame upwards that hasn't an empty note or doesn't start with "-"
local function FindSectionStart(start)
local current = tastudio.find_marker_on_or_before(start)
while bizstring.startswith(tastudio.getmarker(current), "-") or tastudio.getmarker(current) == "" do
current = tastudio.find_marker_on_or_before(current-1)
end
return current
end
-- Finds the first marker beginning from start frame downwards that hasn't an empty note or doesn't start with "-"
local function FindSectionEnd(start, branchId)
local marker = tastudio.find_marker_on_or_before(start, branchId)
local markers = tastudio.get_frames_with_markers(branchId)
for k,v in pairs(markers) do
if v == marker -- Find marker from start frame
then repeat k = k + 1 -- Search the list markers downwards until it finds an marker with a note that doesn't start with "-" or isn't empty
if k > #markers
then return nil
end
until not (bizstring.startswith(tastudio.getmarker(markers[k], branchId), "-") or tastudio.getmarker(markers[k], branchId) == "")
return markers[k]
end
end
end
local function OpenDropdown()
if BranchCount() == 0
then return
end
if open
then open = false
else open = true
end
end
local function SelectPreviousBranch()
if BranchCount() == 0
then return
end
if selectedbranch > 1
then selectedbranch = selectedbranch - 1
end
CaclulateDifferenceToBranchMarker(currentsection)
end
local function SelectNextBranch()
if BranchCount() == 0
then return
end
if selectedbranch < BranchCount()
then selectedbranch = selectedbranch + 1
end
CaclulateDifferenceToBranchMarker(currentsection)
end
-- Finds the section end marker after the current section start
-- Tries to find those two markers in branches
-- Selects the branch with the shortest time between those two markers in that branch
-- If no current section end marker, it finds the section end marker in branches
-- If no section end marker found in branches either, it uses the branch with earliest section start marker
local function SelectBestBranch()
if BranchCount() == 0
then return
end
local startmarker = currentsection
local endmarker = FindSectionEnd(startmarker)
local best = 99999999
local earliest = 99999999
local index = nil -- With branchstartmarker and branchendmarker not nil
local index2 = nil -- Only branchstartmarker not nil
for k, branch in pairs(tastudio.getbranches()) do
local branchmarkers = tastudio.get_frames_with_markers(branch.Id)
local branchstartmarker = nil
local branchendmarker = nil
for _, markerframe in pairs(branchmarkers) do
if tastudio.getmarker(startmarker) == tastudio.getmarker(markerframe, branch.Id)
then branchstartmarker = markerframe
if endmarker == nil
then branchendmarker = FindSectionEnd(branchstartmarker, branch.Id)
end
elseif endmarker ~= nil and tastudio.getmarker(endmarker) == tastudio.getmarker(markerframe, branch.Id)
then branchendmarker = markerframe
end
if branchstartmarker ~= nil and branchendmarker ~= nil or branchstartmarker ~= nil and endmarker == nil
then break
end
end
if branchstartmarker ~= nil and branchendmarker ~= nil and branchendmarker - branchstartmarker < best
then best = branchendmarker - branchstartmarker
index = k
elseif branchstartmarker ~= nil and branchendmarker == nil and branchstartmarker < earliest
then earliest = branchstartmarker
index2 = k
end
end
if endmarker == nil and index == nil and index2 ~= nil -- No section end marker found in branches
then selectedbranch = index2 + 1
elseif index ~= nil
then selectedbranch = index + 1
end
CaclulateDifferenceToBranchMarker(currentsection)
end
local clickitems = {{x1=200, y1=18, x2=216, y2=2, clickFunction = OpenDropdown, text = "▲"},
{x1=220, y1=18, x2=236, y2=2, clickFunction = SelectPreviousBranch, text = "-"},
{x1=240, y1=18, x2=256, y2=2, clickFunction = SelectNextBranch, text = "+"},
{x1=260, y1=18, x2=302, y2=2, clickFunction = SelectBestBranch, text = "Best", xt=260, yt=18, size=16}}
local function DrawDropdown(mousex, mousey)
local text = tastudio.getmarker(currentsection)
for k, branch in pairs(tastudio.getbranches()) do
if k == selectedbranch - 1
then gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF606060, "client")
else gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF202020, "client")
end
if mousex > 60 and mousex < 200 and mousey > client.screenheight()-18-16*(k+1) and mousey < client.screenheight()-2-16*(k+1)
then gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF808080, "client")
if input.getmouse()["Left"] and not mousedown
then open = false
if k ~= selectedbranch - 1
then selectedbranch = k+1
CaclulateDifferenceToBranchMarker(currentsection)
end
end
end
local diff = ""
for _, frame in pairs(tastudio.get_frames_with_markers(tastudio.getbranches()[k].Id)) do
if text == tastudio.getmarker(frame, tastudio.getbranches()[k].Id)
then diff = tostring(frame - currentsection)
break
end
end
local branchtext = "#"..(k+1)..": "..(branch.Text or "").." "..diff
if #branchtext >= 17
then branchtext = bizstring.remove(branchtext, 17-#diff-1, #branchtext-17)
end
gui.drawText(65, client.screenheight()-18-16*(k+1), branchtext, nil, nil, nil, nil, nil, nil, nil, "client")
end
end
local function DrawClickItems(mousex, mousey)
for k,v in pairs(clickitems) do
gui.drawBox(v.x1, client.screenheight()-v.y1, v.x2, client.screenheight()-v.y2, 0xFFFFFFFF, 0xFF202020, "client")
if mousex > v.x1 and mousex < v.x2 and mousey > client.screenheight()-v.y1 and mousey < client.screenheight()-v.y2
then gui.drawBox(v.x1, client.screenheight()-v.y1, v.x2, client.screenheight()-v.y2, 0xFFFFFFFF, 0xFF808080, "client")
if input.getmouse()["Left"] and not mousedown
then v.clickFunction()
end
end
gui.drawText(v.xt or v.x1-3, v.yt and client.screenheight()-v.yt or client.screenheight()-v.y1-4, v.text, nil, nil, v.size or 24, nil, "bold", nil, nil, "client")
end
end
local function StatusBar()
gui.drawText(2, client.screenheight()-18, "Branch:", nil, nil, nil, nil, nil, nil, nil, "client")
gui.drawBox(60, client.screenheight()-18, 200, client.screenheight()-2, 0xFFFFFFFF, 0xFF202020, "client")
if BranchCount() > 0 and tastudio.getbranches()[selectedbranch-1]
then local branchtext = "#"..selectedbranch..": "..(tastudio.getbranches()[selectedbranch-1].Text or "")
if #branchtext >= 17
then branchtext = bizstring.remove(branchtext, 17, #branchtext-17)
end
gui.drawText(65, client.screenheight()-18, branchtext, nil, nil, nil, nil, nil, nil, nil, "client")
end
gui.drawText(308, client.screenheight()-18, "Section: "..tostring(tastudio.getmarker(currentsection)).." "..tostring(markerdiff), nil, nil, nil, nil, nil, nil, nil, "client")
local mousex = input.getmouse()["X"]*(client.screenwidth()) / (client.bufferwidth())
local mousey = input.getmouse()["Y"]*(client.screenheight()-20) / (client.bufferheight())
DrawClickItems(mousex, mousey)
if open == true
then DrawDropdown(mousex, mousey)
end
end
----------------
-- Branches --
----------------
local function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
copy = {}
for orig_key, orig_value in next, orig, nil do
copy[deepcopy(orig_key)] = deepcopy(orig_value)
end
setmetatable(copy, deepcopy(getmetatable(orig)))
else copy = orig -- number, string, boolean, etc
end
return copy
end
-- Called when a branch is loaded
local function BranchLoad(index)
if index >= 0
then branches[-1] = {} -- backup for undo branch load
for k,v in pairs(Logs) do
local vals = deepcopy(Logs[k].values)
branches[-1][k] = {values = {}}
branches[-1].values = vals
end
end
-- TODO:Check ungreenframe and copy from current log if those are valid for branch log
for k,v in pairs(Logs) do
local vals = deepcopy(Logs[k].values)
oldLogs[k].values = vals
if branches[index+1] and branches[index+1][k] ~= nil
then local vals = deepcopy(branches[index+1][k].values)
Logs[k].values = vals
else Logs[k].values = {} -- Reset current log values in case there is no log for loaded branch
end
end
currentsection = FindSectionStart(emu.framecount())
CaclulateDifferenceToBranchMarker(currentsection)
end
-- Called when a branch is saved
local function BranchSave(index)
if index >= 0
then branches[index+1] = {}
for k,v in pairs(Logs) do
local vals = deepcopy(Logs[k].values)
branches[index+1][k] = { values = {} }
branches[index+1][k].values = vals
end
end
SaveFile(index)
end
-- Called when a branch is removed
local function BranchRemove(index)
--TODO: use branch id, not possible in 2.11, onbranchremove called after the branch is deleted, it will get id of next branch
if selectedbranch >= BranchCount()
then selectedbranch = 1
end
branches[-1] = {}
for k,v in pairs(Logs) do -- make backup
local vals = deepcopy(Logs[k].values)
branches[-1][k] = {values = {}}
branches[-1][k].values = vals
end
if branches[index+1]
then table.remove(branches, index+1)
end
local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj")
--os.remove(filename.."_"..tostring(index+1).."_deleted.log") -- delete the backup
os.remove(filename.."_deleted.log") -- delete the backup
os.rename(filename.."_"..tostring(index+1)..".log", filename.."_deleted.log") -- rename for backup
for i = index+1, BranchCount(), 1 do
os.rename(filename.."_"..tostring(i+1)..".log", filename.."_"..tostring(i)..".log")
end
end
----------
-- Main --
----------
-- Called after each frame
local function LogValues()
for k, v in pairs(Logs) do
if emu.framecount() >= ungreenframe -- only change the old log for ungreenzoned frames
then oldLogs[k].values[emu.framecount()] = Logs[k].values[emu.framecount()]
end
Logs[k].values[emu.framecount()] = Logs[k].expression() -- Log values
end
end
local function FrameEnd()
if emu.framecount() > ungreenframe
then ungreenframe = emu.framecount()
end
LogValues()
end
-- Called when the script is closed
local function Exit()
-- if not loaderror -- Don't save the Log to current Log, if there was an error during loading files
-- then
-- end
SaveFile(-2)
SaveFile(-3, oldLogs)
-- Empty tables for script reloading
Logs = {}
Printers = {}
branches = {}
oldLogs= {}
end
function UpdateLogger()
if currentmarker ~= lastmarker
then currentsection = FindSectionStart(emu.framecount())
CaclulateDifferenceToBranchMarker(currentsection)
end
lastmarker = currentmarker
currentmarker = tastudio.find_marker_on_or_before(emu.framecount())
StatusBar()
mousedown = input.getmouse()["Left"]
end
function InitializeLogger()
if tastudio.engaged() == false
then console.log("WARNING: This script only works with TAStudio open.")
else selectedbranch = 1
ungreenframe = 0
currentmarker = tastudio.find_marker_on_or_before(emu.framecount())
currentsection = FindSectionStart(emu.framecount())
for i = 0, BranchCount()-1, 1 do
local logs_ = {}
LoadFile(i, logs_)
if logs_[next(Logs)] -- Checks if the first key of Logs table is also a key of logs_ table. Doing the check here saves nil checks for Log value functions
then branches[i+1] = logs_
end
end
LoadFile(-3, oldLogs)
LoadFile(-2, Logs) -- Load files saved when script is stopped
tastudio.onqueryitemtext(TAStudioText)
tastudio.onqueryitembg(TAStudioColor)
tastudio.ongreenzoneinvalidated(Ungreen)
tastudio.onbranchsave(BranchSave)
tastudio.onbranchload(BranchLoad)
tastudio.onbranchremove(BranchRemove)
event.onframeend(FrameEnd)
event.onexit(Exit)
client.SetClientExtraPadding(0,0,0,20)
end
end