Skip to content

Instantly share code, notes, and snippets.

@FichteFoll
Created December 24, 2012 12:54
Show Gist options
  • Save FichteFoll/4369153 to your computer and use it in GitHub Desktop.
Save FichteFoll/4369153 to your computer and use it in GitHub Desktop.
Oreimo ED13 [True Route] Karaoke-FX in Lua
-- This script is made by FichteFoll for Akatsuki-Subs.net
-- You can see this FX in Ore no Imouto TR [True Route] 13 (Ending)
--[[
Remarks:
This script uses `aegisub.cancel` function which was implemented in
r6325, however, it works without it.
I would've used some operation overloading inspired from Python ((probably) up-to-date version here:
https://gist.github.com/b6815727db496ceed1ae) but Aegisub does not provide the debug module which is required to
set a types metatable that does not yet have one (everything except string).
2012-12-24
]]
require("karaskel")
require("op_overloads")
-- use the never-changing parts for my Automation FXes from this include file
require("FX_utils")
script_name = "Oreimo TR13 FX"
script_description = "Nothing yet"
script_author = "FichteFoll"
script_version = 0.8
math.randomseed(1337)
--------------------------------------------------------------------------------
function round(num, idp)
local mult = 10^(idp or 0)
if num >= 0 then return math.floor(num * mult + 0.5) / mult
else return math.ceil(num * mult - 0.5) / mult end
end
function randomfloat(min, max)
return math.random() * (max - min) + min
end
function string.concat(...)
ret = ""
for _, str in pairs{...} do
-- there shouldn't be any nils here
ret = ret..tostring(str)
end
return ret
end
--------------------------------------------------------------------------------
-- variables/constants
local tmp = {
px_per_ms = 1,
px_per_ms_nokara = 3,
mid = 0.7,
hl_mid = 0.1,
frag_accel = 0.4,
hl_accel = 0.4,
fade_dur = 400
}
local _frag = {
width = 1,
fadein = {
accel1 = tmp.frag_accel,
accel2 = 1.0 / tmp.frag_accel * (tmp.mid / (1.0 - tmp.mid)) -- this makes absolutely no sense
},
bord_extend = 2
}
tmp.diff = 1.0 * _frag.width / tmp.px_per_ms
tmp.diff2 = 1.0 * _frag.width / tmp.px_per_ms_nokara
local _line = {
fadein = {
accel = 0.5,
dur = tmp.fade_dur,
diff = tmp.diff,
mid = tmp.mid * tmp.fade_dur,
scale = 1.4,
start = -500
},
fadeout = {
accel = 3,
dur = tmp.fade_dur,
diff = tmp.diff,
mid = tmp.mid * tmp.fade_dur,
scale = 4.0,
start = -500
},
blur = 1.5
}
local _syl = {
hl = {
accel1 = tmp.hl_accel,
accel2 = 1.0 / tmp.hl_accel * (tmp.hl_mid / (1.0 - tmp.hl_mid)), -- this makes absolutely no sense, too
blur = 3,
bord = 3.5,
extend = 350,
mid = tmp.hl_mid,
minscale = 1.2,
maxscale = 2.2
}
}
--------------------------------------------------------------------------------
local log
function init(subs, meta, styles)
log = aegisub.log
log("_frag.width = %d, _line.fadein.diff = %d, px_per_ms = %d\n\n",
_frag.width, _line.fadein.diff, tmp.px_per_ms)
end
function do_fx(subs, meta, styles, line, baseline)
-- pre-process1
parse_style_colors(line, styles[line.style])
if (line.style == "ed13_rom" or line.style == "ed13_kan" or line.style == "ed13_ger") then
-- pre-process2
karaskel.preproc_line(subs, meta, styles, line)
parse_syls(line)
local bord = line.styleref.outline + _frag.bord_extend
-- use this table as list of all the fragments (with their values and stuff)
local frags = {n = math.ceil((line.width + 2*bord) / _frag.width)}
-- use this table as a list of the frags that should be added to subs
-- I need to do this extra step because
-- 1) there may be multiple syllables in one fragment and
-- 2) I need to know how many frags a syl intersects. I could use math for that but apparently this works just
-- as well.
local real_frags = {}
-- use to differentiate between karaoke styles and the actual subtitles
local kara = line.style ~= "ed13_ger"
log("li: %d, width: %d, #frags: %d, style: %s, kara: %q\n",
line._i, line.width, frags.n, line.style, tostring(kara))
-- parse the color overrides on the line (assuming they are the only blocks before the {\k}-block)
line._colors = {_all = ""}
local colors_str, text = line.text:match("^(.-)({\\k.+)$")
if not colors_str then
-- we are probably in a "sub" block, just consume all the override blocks
colors_str = ""
text = line.text:gsub("({.-})", function (override)
colors_str = colors_str .. override
return ""
end)
end
if not colors_str then log("No match on line: %s\n", line.text); end
if colors_str and #colors_str > 0 then
-- collect color blocks
for str in colors_str:gmatch("{(.-)}") do
-- always assuming there is only one \t override tag each
local start, stop, colors = str:match("\\t%((%d+),(%d+),(%S-)%)")
-- log("start: %s, stop: %s, colors: %s\n" % {start or "nil", stop or "nil", colors or "nil"})
if not colors then
colors = str:match("\\t%((%S-)%)")
end
local block = {
text = str,
start = tonumber(start) or 0,
stop = tonumber(stop) or (colors and line.duration or 0),
colors = colors or str
}
if not colors then -- supposed to happen only once
line._colors._base = block
else
table.insert(line._colors, block)
end
line._colors._all = line._colors._all .. str
end
-- log(repr(line._colors) .. "\n")
end
-- iterate over the fragments and create fadein and fadeout
for i = 1, frags.n do
local l = table.copy(line)
local frag = {l = l, hl = table.copy(l)}
table.insert(frags, frag)
-- calc some :values:
frag.left = line.left - bord + (i - 1) * _frag.width
frag.right = frag.left + _frag.width
frag.start_rel = round(_line.fadein.start + (i - 1) * (kara and _line.fadein.diff or tmp.diff2))
frag.start_rel_actual = math.floor(frag.start_rel / 10.0) * 10 -- ASS only allows centiseconds in timestamps
frag.start_diff = frag.start_rel - frag.start_rel_actual -- 10 > x >= 0
frag._pos = "\\an5\\pos(%d,%d)" % {line.center, line.middle} -- will be overwritten
frag._text = l.text_stripped
frag._pre = "\\clip(%d,%d,%d,%d)\\blur%.1f" % {frag.left, 0, frag.right, meta.res_y, _line.blur}
-- the colors
if #line._colors._all > 0 then
frag._colors = shift_ttags(line._colors._all, frag.start_rel):gsub("\\2c&H%w+&", "")
end
--func_shift_by(frag.start_rel))
-- fadein
local fadein = "\\alpha&HFF&\\t(%d,%d,%.2f,\\1a%s\\3a%s\\4a%s)" % { -- step1
frag.start_diff, frag.start_diff + _line.fadein.dur, _line.fadein.accel,
line._a[1], line._a[3], line._a[4]
}
fadein = fadein .. "\\fscy0\\t(%d,%d,%.2f,\\fscy%.2f)" % { -- step2
frag.start_diff, frag.start_diff + _line.fadein.mid, _frag.fadein.accel1,
_line.fadein.scale * line.styleref.scale_y
}
fadein = fadein .. "\\t(%d,%d,%.2f,\\fscy%.2f)" % { -- step3
frag.start_diff + _line.fadein.mid, frag.start_diff + _line.fadein.dur,
_frag.fadein.accel2, line.styleref.scale_y
}
frag._fadein = fadein
-- fadeout
frag.fadeout_start = round(300 + line.duration + frag.start_rel + _line.fadeout.start
- (frags.n - i) * (kara and _line.fadeout.diff or tmp.diff2)+ _syl.hl.extend)
-- just add 300 for considering the last syllable not "ending" with the last frag
local fadeout = "\\t(%d,%d,%.2f,\\alpha&HFF&\\fscy%.2f)" % { -- step1
frag.fadeout_start, frag.fadeout_start + _line.fadeout.dur, _line.fadeout.accel,
_line.fadeout.scale * line.styleref.scale_y
}
frag._fadeout = fadeout
frag.end_rel = frag.fadeout_start + _line.fadeout.dur
-- line timestamps
l.start_time = l.start_time + frag.start_rel_actual
l.end_time = l.start_time + (frag.end_rel / 10 + 1) * 10 -- why the math?
end
if kara then
-- hl
for si, syl in ipairs(line._noblank) do
log("\ntext: %s\n", syl.text_stripped)
syl._left = line.left + syl.left - bord
syl._right = line.left + syl.right + bord
syl._center = line.left + syl.center
local startfrags = #real_frags
for _, bfrag in ipairs(frags) do
-- collect interleaving fragments
if ( (bfrag.left <= syl._left and syl._left < bfrag.right)
or (bfrag.left < syl._right and syl._right <= bfrag.right)
or (syl._left <= bfrag.left and bfrag.left < syl._right)
or (syl._left < bfrag.right and bfrag.right <= syl._right)) then
frag = table.copy(bfrag)
frag._si = si
table.insert(real_frags, frag)
-- some variables
frag._pos = "\\an5\\pos(%d,%d)" % {syl._center, line.middle}
frag._text = syl.text_stripped
frag.hl_start_rel = -frag.start_rel_actual + syl.start_time
frag.hl_mid = syl.duration * _syl.hl.mid
frag.hl_end = syl.duration + _syl.hl.extend
-- hl line timestamps
frag.hl_start_time = frag.l.start_time + frag.hl_start_rel
frag.hl_end_time = frag.hl_start_time + frag.hl_end
end
end
-- log("start: %d, mid: %d, end: %d\n", syl.start_time, syl.start_time + frag.hl_mid, syl.start_time + frag.hl_end)
-- if frag._text == "no?" then log("cols: %s\n", repr(line._colors)); end
-- log("start: %s\n" % color_at(line._colors, syl.start_time, 1))
-- log("mid: %s\n" % color_at(line._colors, syl.start_time + frag.hl_mid, 2))
-- log("end: %s\n" % color_at(line._colors, syl.start_time + frag.hl_end, 1))
local numfrags = #real_frags - startfrags
local midfrags = numfrags / 2 -- auto-flooring with integers
-- log("#frags %d|%.1f\n", numfrags, midfrags)
-- build the hl overrides
for i = 1, numfrags do
frag = real_frags[startfrags + i]
local scale = _syl.hl.minscale
+ math.sin(math.pi / 2 -- use a sinus curve
* (i <= midfrags and i or (numfrags - i + 1)) / midfrags)
* (_syl.hl.maxscale - _syl.hl.minscale)
-- if not frag._text == "yo" then log("col: %s\n", line._colors); end
frag._hl_colors = "\\1c%s" % (color_at(line._colors, syl.start_time, 1) or line._c[1])
.. shift_ttags(line._colors._all, syl.start_time):gsub("\\[21]?c&H%w+&", "")
-- log(hl.."\n")
frag._hl = "\\t(0,%d,%.2f,\\1c%s\\1a%s\\fscy%.2f\\blur%.1f)" % { -- "in hl"
frag.hl_mid, _syl.hl.accel1,
color_at(line._colors, syl.start_time + frag.hl_mid, 2) or line._c[2], line._a[2],
scale * line.styleref.scale_y, _syl.hl.blur
}
.. "\\t(%d,%d,%.2f,\\1c%s\\1a%s\\fscy%.2f\\blur%.1f)" % { -- "out hl"
frag.hl_mid, frag.hl_end, _syl.hl.accel2,
color_at(line._colors, syl.start_time + frag.hl_end, 1) or line._c[1], line._a[1],
line.styleref.scale_y, _line.blur
}
-- make the main line invisible during hl
frag._invisible = "\\t(%d,%d,\\alpha&HFF&)\\t(%d,%d,\\1a%s\\3a%s\\4a%s)" % {
frag.hl_start_rel, frag.hl_start_rel,
frag.hl_start_rel + frag.hl_end, frag.hl_start_rel + frag.hl_end,
line._a[1], line._a[3], line._a[4]
}
end
end
else
real_frags = frags -- we need all the fragments
end
log("#real_frags: %d\n\n", #real_frags)
for _, frag in ipairs(real_frags) do
-- order matters
frag.l.text = string.concat(
'{', frag._pos, frag._pre, frag._fadein, frag._colors, frag._invisible or "", frag._fadeout, '}', frag._text
)
subs.append(frag.l)
if kara then
frag.hl.text = string.concat(
'{', frag._pos, frag._pre, frag._hl_colors, frag._hl, '}', frag._text
)
frag.hl.start_time = frag.hl_start_time
frag.hl.end_time = frag.hl_end_time
subs.append(frag.hl)
end
end
return 1
end
return -- not processed
end
-- Searches for \t-tag times and shifts them by `by`
function shift_ttags(str, by)
return str:gsub("\\t%((%d+),(%d+)", function (start, stop)
return "\\t(%d,%d" % {tostring(tonumber(start) - by),
tostring(tonumber(stop) - by)}
end)
end
-- Returns the selected color `i` (or the first one) at `timestamp` given a few override codes.
-- Does not check for validity anywhere; do not abuse. Returns nil if color `i` was not found.
function color_at(col, timestamp, i)
if not i then i = "[1-4]"; end -- match any color number in the pattern
if i == 1 then i = "1?"; end -- 1 can be omitted
function _ret(col)
-- used to select the specified color "i"; nil if not found
return col.colors:match("\\"..i.."c(&H%w+&)") or nil
end
local active, last
-- iterate over override blocks for the currently active transition, consider only the last
local start, stop
for _, block in ipairs(col) do
if (timestamp > block.start and timestamp < block.stop and _ret(block)) then
active = block
end
end
-- ... for the lastly active transition
for _, block in ipairs(col) do
if (timestamp > block.stop and _ret(block)) then
last = block
end
end
if not last then last = col._base; end
if not active then
return _ret(last) -- just return the last color
end
-- select the specified color
local start, stop = _ret(last), _ret(active)
-- either no color or no "new" color found
if not stop then
-- log("stcol: %s\n", last.colors)
-- log("start: %s\n", start)
return start or nil
end
-- interpolate the color
local pct = (timestamp - active.start) * 1.0 / (active.stop - active.start)
return interpolate_color(pct, start, stop)
end
--[[
Outline:
- Split the syl into stripes, 1px
- Add \clips to them
- Animations possible:
- Animating vertically
- Moving vertically/horizontally
- Border/Shadow/Blur (if the area is large enough)
Fadein:
- left-to-right
- Timeline:
[----------step1--------]
[-----step2-----|-step3-]
step1 (accelerated)
- \alpha&HFF& -> whaever alpha is set in the styles
step2 (accelerated)
- \fscy<100 -> \fscy>100
step3 (accelerated)
- \fscy>100 -> \fscy<style>
Fadeout:
- left-to-right
- Timeline:
[---------step1---------]
step1 (accelerated)
- grow, \alpha&HFF&
Highlight:
- Effect:
Pop up (vertically) with increased size toward the middle
Also animate border, blur, color
- Timeline:
[-step1-|-----step2-----]
step1 (accelerated)
- grow
step2 (accelerated)
- shrink
- Process:
- Iterate over fragments
- Check whether the fragment is in the syllable range
- Dublicate the frag, add it to a separate table
- Check how many fragments are interleaving the syllable and calculate the scale toward the mid
The Color Thing:
- 2 layers: the main line and the hl layer
the hl line should only appear on the hl, the other line will be invisible during that
x parse line for \c, \a and \t tags (well, just everything)
x strip \2c for the main line
x parse the times from \t tags and shift them according to the fadein (frag.start_rel) (or the hl)
x somehow calculate the "current" \2c color (which is \1c then) for the hl line
Subtitles:
x just don't apply hl strings to them
x use different px_per_ms constant
]]
register()
--[[
A template script used for Automation 4 karaoke FXes with Aegisub
You will want to call `register()` at the end of your script or whenever at least `script_name` is defined.
Functions to be defined:
* init(subs, meta, styles) [optional]
Called after all possibly existing previous fx lines have been deleted and the karaoke lines restored.
Do whatever you like in here. Won't be called if `init` is nil.
@subs
The subs table passed by aegisub.
@meta, @styles
Results of karaskel.collect_head(subs, false).
* do_fx(subs, meta, styles, line, baseline)
Called when is_parseble(line) returns `true`, i.e. when you should parse that line.
The parsed line will be commented and its effect set to "karaoke" iff this function returns a value evaluating
to `true`.
@subs
Same as for init().
@meta, @styles
Same as for init().
@line
This is the line source you will be using. You can do anything with it, it's in fact a table.copy of
@baseline with its effect set to "fx" and layer to 2.
Has a field named "_i" which defines the line's index in `subs` and a field named "_li" which counts up
every time do_fx is called.
@baseline
The table representing the line.
This is a REFERENCE, you need to table.copy() this before if you want to insert a new copy.
The line itself should not be modified at all, e.g. when you plan to run this macro a few times.
Also defines "_i" and "_li" (see @line).
Variables to be defined (before calling `register()`):
* script_name
By FichteFoll, last modified: 2012-12-24
]]
require("karaskel")
-- ############################# handler funcs #################################
function macro_script_handler(subs)
aegisub.progress.title("Apply "..script_name)
script_handler(subs)
aegisub.set_undo_point('"Apply '..script_name..'"')
end
function script_handler(subs)
aegisub.progress.task("Getting header data...")
local meta, styles = karaskel.collect_head(subs, false)
-- undo the fx before parsing the karaoke data
aegisub.progress.task("Removing old karaoke lines...")
undo_fx(subs)
aegisub.progress.task("Applying effect...")
local i, maxi = 1, #subs
local li = 0
if init then
init(subs, meta, styles)
end
while i <= maxi do
local l = subs[i]
if aegisub.progress.is_cancelled() then
if aegisub.cancel then aegisub.cancel() end
return
end
aegisub.progress.task(string.format("Applying effect (%d/%d)...", i, maxi))
aegisub.progress.set((i-1) / maxi * 100)
if is_parseable_line(l) then
-- karaskel.preproc_line(subs, meta, styles, l)
li = li + 1
l._li = li
l._i = i
-- prepare the to-be-copyied line
local line = table.copy(l)
line.effect = "fx"
line.layer = 2
if do_fx(subs, meta, styles, line, l) then
l.comment = true
l.effect = "karaoke"
end
subs[i] = l
end
i = i + 1
end
aegisub.progress.task("Finished!")
aegisub.progress.set(100)
end
function undo_macro_script_handler(subs, ...)
aegisub.progress.title("Undoing "..script_name)
undo_fx(subs)
aegisub.set_undo_point("\"Undo "..script_name.."\"")
end
function undo_fx(subs)
aegisub.progress.task("Unapplying effect...")
local i, maxi = 1, #subs
local ai, maxai = i, maxi
while i <= maxi do
if aegisub.progress.is_cancelled() then
if aegisub.cancel then aegisub.cancel() end
return
end
aegisub.progress.task(string.format("Unapplying effect (%d/%d)...", ai, maxai))
aegisub.progress.set((ai-1) / maxai * 100)
local l = subs[i]
if (l.class == "dialogue" and not l.comment and l.effect == "fx") then
subs.delete(i)
maxi = maxi - 1
else
if (l.class == "dialogue" and l.comment and l.effect == "karaoke") then
l.comment = false
l.effect = ''
subs[i] = l
end
i = i + 1
end
end
end
-- ############################### parsing funcs ###############################
function parse_syls(line)
-- parse the line's syllables and add them to the `line._noblank` table
-- requires you to call karaskel.preproc_line first
line._noblank = {n = 0}
for i = 1, line.kara.n do
local syl = line.kara[i]
if (syl.duration > 0 and syl.text_stripped ~= ''
and syl.text_stripped ~= ' ' and syl.text_stripped ~= ' ') then
line._noblank.n = line._noblank.n + 1
line._noblank[line._noblank.n] = syl
syl.i = line._noblank.n
syl._blank = false
else
syl._blank = true
end
end
end
function parse_style_colors(line, style)
line._c = {}
line._a = {}
for i, c, a in colors_from_style(style) do
line._c[i] = c
line._a[i] = a
end
end
-- ############################### helping funcs ###############################
function repr(val)
if type(val) == "table" then
local str = "{" --"#%d:{" % #val
for k, v in pairs(val) do
str = str .. ("%s = %s, "):format(k, repr(v))
end
return str:sub(1, -3) .. "}" -- trim last ", "
elseif type(val) == "string" then
return '"%s"' % val
else
return tostring(val)
end
end
function colors_from_style(style)
local i = 0
function enum_colors_from_style()
i = i + 1
if (i > 4) then
return nil
end
-- i, color, alpha
return i, color_from_style(style["color"..tostring(i)]), alpha_from_style(style["color"..tostring(i)])
end
return enum_colors_from_style, nil, nil
end
function strip_comments(str)
return str:gsub("{.-}", "")
end
function xor(...)
-- return the first parameter which evaluates to `true`
args = {...}
for v in args do
if v then return v; end
end
return args[#args] -- return the last element if none is true
end
function _if(test, a, b)
return test and a or b
end
function is_parseable_line(l)
return (l.class == "dialogue" and not l.comment)
or (l.class == "dialogue" and l.comment and l.effect == "karaoke")
end
-- ############################### validation funcs ############################
function macro_validation(subs)
for i = 1, #subs do
local l = subs[i]
if is_parseable_line(l) then
return true
end
end
return false, "No parseable line found"
end
function undo_macro_validation(subs)
for i = 1, #subs do
local l = subs[i]
if (l.class == "dialogue" and (
(not l.comment and l.effect == "fx") or
( l.comment and l.effect == "karaoke") )) then
return true
end
end
return false, "No karaoke line found"
end
-- ############################## registering ##################################
function register()
aegisub.register_macro("Apply "..script_name, "Processing script as templater", macro_script_handler, macro_validation)
-- should not be needed (for my setup at least, I have a separate macro which does this)
-- aegisub.register_macro("Undo "..script_name, "Removing templated lines", undo_macro_script_handler, undo_macro_validation)
end
--[[
These are small operator overloads for general use in Lua, mainly to my likings.
Written by FichteFoll; thanks to Sleepy_Coder
2012-12-24
]]
local STR, NUM, BOOL, NIL = {}, {}, {}, {}
STR = getmetatable('')
-- ("hello")[2] -> e
STR.__index
= function (self, key)
if type(key) == 'number' then
if key < 1 or key > self:len() then
error(("Attempt to get index %d which is not in the range of the string's length"):format(key), 2)
end
return self:sub(key, key)
end
return string[key]
end
-- str = "heeeello"; str[3] = "is" -> "heisello" || DOES NOT WORK!
STR.__newindex =
function (self, key, value)
value = tostring(value)
if type(key) == 'number' and type(value) == 'string' then
if key < 1 or key > self:len() then
error(("Attempt to set index %d which is not in the range of the string's length"):format(key), 2)
end
-- seems like strings are not referenced ...
self = self:sub(1, key-1) .. value .. self:sub(key+value:len(), -1)
-- print(("new value: %s; key: %d"):format(self, key))
return self
end
end
-- string * num -> string.rep
STR.__mul =
function (op1, op2)
return type(op2) == 'number' and op1:rep(op2) or error("Invalid type for arithmetic on string", 2)
end
-- string % table -> string.format
STR.__mod =
function (op1, op2)
if type(op2) == 'table' then
if #op2 > 0 then
-- make `nil` to string
for k,v in pairs(op2) do
if v == nil then op2[k] = "nil"; end
end
return op1:format(unpack(op2)) -- sadly I can not forward errors happening here
else
error("Format table is empty", 2)
end
else
return op1:format(op2 == nil and "nil" or op2)
end
end
-- #string -> count chars
STR.__len =
function (self)
return self:len()
end
-- e.g. num = 1234.5; num:floor()
NUM.__index = math
-- rarely useful
BOOL.__index = BOOL
-- a wrapper for boolean tests
BOOL.b2n =
function (bool)
return type(bool) ~= 'boolean' and bool or (bool and 1 or 0)
end
-- various arithmetics on booleans by converting `false` to `0` and `true` to `1`.
-- `nil` will be converted to `0` as well, btw.
BOOL.__add =
function (op1, op2)
-- op2.b2n() won't work because it is possible that one of these ops is not a boolean
return BOOL.b2n(op1) + BOOL.b2n(op2)
end
BOOL.__sub =
function (op1, op2)
return BOOL.b2n(op1) - BOOL.b2n(op2)
end
BOOL.__mul =
function (op1, op2)
return BOOL.b2n(op1) * BOOL.b2n(op2)
end
BOOL.__div =
function (op1, op2)
return BOOL.b2n(op1) / BOOL.b2n(op2)
end
BOOL.__pow =
function (op1, op2)
return BOOL.b2n(op1) ^ BOOL.b2n(op2)
end
BOOL.__unm =
function (self)
return not self
end
-- copy BOOL's functions over to NIL and remove a few values
for key, val in pairs(BOOL) do
NIL[key] = val
end
NIL.b2n = nil
NIL.__unm = nil
-- nil[3] -> nil (no error) - is this behaviour useful?
NIL.__index = NIL
-- Apparently, Aegisub does not provide the debug module ...
if debug then
debug.setmetatable( 0, NUM )
debug.setmetatable(true, BOOL)
debug.setmetatable( nil, NIL )
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment