2018年12月25日火曜日

日本時間でのリセット設定


diggerアドオンを見ていたら以下のコードがあった。
local today = os.date('!%Y-%m-%d', os.time() + 32400)

32400秒は9時間であるためUTC+9つまり日本標準時JSTを指定している。なお、Luaのos.dateで!を使うとUTCで書式化される決まり。それを9時間ずらしてどのタイムゾーンでも日本時間の日付を取得できる。

たとえば、日本時間1月1日AM8時は、ニューヨークだと前日のPM6時。14時間の時差があり日付が異なる状態となる。でも、ニューヨークで稼働しているWindowerも新年1月1日の日付がtodayに格納される。


diggerアドオンは、このtodayの値をsettings(config)を使ってファイルに保存し、地球時間0時またぎのリセットが発生したかどうかを検出している。

res.itemsはnameエントリで言語差を吸収している


サメーさんのShopperアドオンの中身を見ていたら
function validate_item(npc_item)
 for i,v in pairs(res.items) do
  if v.name:lower() == npc_item then
   return v.id
  end
 end
end
こんなコードがあった。res.itemsはres\items.luaの中身が入っている。でも、その中にはnameというエントリは含まれていない。アイテムの名前はenやjaで登録されている。

v.nameとはいったい何だろうか。

addons\lib\resources.luaには以下のコードがある。
local language_string = _addon and _addon.language and _addon.language:lower() or windower.ffxi.get_info().language:lower()

local redict = {
 name = language_string,
 name_log = language_string_log,
 name_short = language_string_short,
 english = 'en',
 japanese = 'ja',
 english_log = 'enl',
 japanese_log = 'ja',
 english_short = 'ens',
 japanese_short = 'jas',
}

-- The metatable for a single resource item (an entry in a sub table of the root resource table)
local resource_entry_mt = {__index = function()
 return function(t, k)
  return redict[k] and t[redict[k]] or table[k]
 end
end()}

このコードから予想できるのは、windowerの言語設定がjapaneseである場合、nameはjaのエイリアスとして機能するということ。

つまり、
item['name'] = item[redict['name']] = item['japanese'] = item[redict['japanese']] = item['ja']


そして、Windowerのwikiにもそう書かれていた。
The general structure of the resources can be gleaned by checking out the respective Lua file in the resources directory. However, there are some deviations. For example, the Lua files use the ISO 639-1 language code for English (en), Japanese (ja), German (de), French (fr), however, in the parsed resources they will appear under their full english names (english, japanese, german and french). Also, a new field name will be added, which is an alias to whatever the addon's language is, or, if none is provided for the addon, what the user's POL language is.


2018年12月13日木曜日

true:fn()について

Windower4\libs\functions.luaの178行目に
function functions.loop(fn, interval, cond)
 ~
 cond = cond or true:fn()
 ~
というコードがある。「true:fn()」はその前後の文脈からすると、常にtrueを返す関数だと思われる。ここでfnは関数loopの引数のことではなく、23行目で定義されている関数となっている。

functions.luaの23行目
for _, t in pairs({functions, boolean, math, string, table}) do
    t.fn = function(val)
        return function()
            return val
        end
    end
end


2018年12月11日火曜日

WindowerのLuaは特殊バージョン

One thing to note is that we don't use stock Lua, but a slightly modified version. We use a superset of actual Lua 5.1, meaning everything that's valid Lua will also be valid for us, but additionally we support a few more things.
Windower Lua APIのページには上記の通り書かれている。Lua ver.5.1を改造したものを使っているとのこと。したがって、一般的なLua実行環境でWindower4\Addons\libs\Tables.luaとかWindower固有の機能を使わない基本的なスクリプトのテストをしようとしてもエラーがでてしまいテスト出来ない。

WindowerのLuaの独自拡張は、2点。

1. 単項プラス演算子の追加
print(+3)

2. 文法規則の変更
x = ('string: %s, number: %d'):format('foo', 42)
        ↓
x = 'string: %s, number: %d':format('foo', 42)
下段は一般的なLua実行環境ではエラーになるがWindowerのLuaでは実行可能。




それ以外にも違いがあった。Tables.luaの73行目
N = function()
 local nt = setmetatable({}, _meta.N)
 return function()
  return nt
 end
end()
最後の行「end()」は一般的なLua実行環境ではエラーになるがWindowerのLuaでは実行可能。もしかすると、「2.文法規則の変更」の範疇なのかもしれない。つまり、

  N = (function() ~end)()

ということか。無名関数を実行してその返り値をNに代入している。

2018年12月10日月曜日

S{} again

S{}は集合のイメージ。
たとえばWindower4\addons\checkparam\checkparam.lua の300行目では
['ayanmo']={item=S{25572,25795,25833,25884,25951},
S{}が使われている。この「S{なんちゃら}」はイメージ的には
 {[25572]=true,[25795]=true,[25833]=true,[25884]=true,[25951]=true}
というテーブルと同じ意味となる。

なお、Ayanmoはアヤモ装備のこと。
25572はアヤモツッケット+2のアイテムID



そうなると、T-テーブルとの違いが気になる。
Windower4\addons\azureSets\azuresets.luaの48行目
defaults.spellsets.vw1 = T{slot01='Firespit', slot02='Heat Breath', slot03='Thermal Pulse',
これはまさにLuaのテーブルの構造と同じ。
T{}によってテーブルにメソッド関数が追加された感じ。

function+table


config.luaの21~23行目
local error = error or print+{'Error:'} local warning = warning or print+{'Warning:'} local notice = notice or print+{'Notice:'}
この「print+{ほにゃらら}」がわけわかめなので調べました。

答えはWindower4\addons\libs\functions.luaの227行目。

-- Assigns a metatable on functions to introduce certain function operators.
-- * fn+{...} partially applies a function to arguments.
-- * fn-{...} partially applies a function to arguments from the end.
-- * fn1..fn2 pipes input from fn2 to fn1.
debug.setmetatable(functions.empty, {
    __index = index,
    __add = add,
    __sub = sub,
    __concat = functions.pipe,
    __unm = functions.negate,
    __class = 'Function'
})

関数の演算子をオーバーロードしてました。+演算子なので__addですね。汎関数(高階関数)は引数として関数を受け取りますが、関数だけでなくその引数も一緒に受け取るイメージです。

たとえば local error = print+{'Error:'} だとして、

 error('a', 'b', 'c')

を実行するとprint('Error:', 'a', 'b', 'c')が実行されるわけです。



マイナス演算子の場合は、local error2 = print-{'Error:'} だとして、

 error2('a', 'b', 'c')

を実行するとprint('a', 'b', 'c', 'Error:')が実行されます。決まり文句を文頭か文末に付け加えるときに便利ってことでしょうか。



これだけだとイマイチこの記法を使う意図がわかりづらいですが、この記法が本領を発揮するのは、たとえば、こんな場合。
require('sets') filter = S{     '* does not have enough *',     'Unable to use *',     '* Left: *',     '* can only use that command during *', } windower.register_event('incoming text', function(text)     return filter:any(windower.wc_match+{text}) end)  
これは、英語フォーラムに投稿されたスクリプトです。日本語クライアントでプレイしているとき、以下のようなメッセージが出てイライラしたりしますよね。

  そのコマンドは実行できない。
  少し時間をおいてから実行してください。


英語フォーラムで提案されていたのは、こういうイライラさせるメッセージをフィルターして出さないようにするスクリプトです。クライアントに送られてくるテキストメッセージのフィルタリングをWindowerの'incoming text'イベントハンドラーを使って実装しています。

'incoming text'の仕様は良く分からないのですが、おそらくreturn trueするとそのメッセージがフィルタされて見えなくなるようです。anyという関数はfunctions.luaで定義されています。wc_matchはワイルドカードの文字列マッチングで、Windowerのwikiに説明がありました

似たようなアイデアはネ実にもありました。


S{}ってなんだ!?

findAll.luaをちょっとだけ読んでみたんだけど、65行目でもうわからないw
    local variable_cache = S{}
なんだこれ。。。
Luaにこんな文法があるのかと一生懸命WEB検索してもわからないw




結論からいうと、findAll.luaの37行目の
require('sets')

これが「S{}」を読み解くカギでした。requireで呼び出しているsets.luaWindower4\addons\libs\sets.luaのこと。その中で
function S(t)
    t = t or {}
    if class(t) == 'Set' then
        return t
    end
    local s = {}
    if class(t) == 'List' then
        for _, val in ipairs(t) do
            s[val] = true
        end
    else
        for _, val in pairs(t) do
            s[val] = true
        end
    end
    return setmetatable(s, _meta.S)
end

このように「S」が定義されてます。つまり「S」は関数名でした。

そして、

   local variable_cache = S{}

というのは

   local variable_cache = S({})

の省略形です。関数にテーブルを1つだけ渡す場合、括弧()を省略できるというLuaのルールがあるそうです。

そして、このSには+とか-とかの演算子がsets.luaの中で定義されていて、集合(sets)どうしの足し算とか引き算とかができるようになっているというわけですね。

イメージ的には、こんなかんじ。

   S{1,2,3}+S{4,5,6} → S{1,2,3,4,5,6}


集合Sと同じようにテーブルTもwindower4\addons\lib\tables.luaのなかで定義されています。
詳しくは下記URLを。

https://github.com/Windower/Lua/wiki/Writing-Addons#tables

windowerのLuaを勉強する


最近、Windowerのaddonのソースコードを見てるんだけど、
そこで勉強したことをこのブログに書いていきます。


https://github.com/Windower/Lua/wiki


なお、だいたいのことは、Windowerのwikiに書かれています。
でも英語なので読むのはしんどい。