かばん

windowerからのマイバッグやその他の収納の扱い方について調べてみたのでメモしておきます。
(ストレージの拡張が予定されているようなので今後仕様が変わるかもしれません。

API

windower.ffxi.get_bag_info(bag)
収納の情報(現在のアイテム数、最大収納数、利用可能かどうか)
windower.ffxi.get_items(bag, index)
収納に入っているアイテムの情報を取得できる。
get_bag_infoで取得できる情報も取得できる。
res/bags.luaにあるidを指定すると、指定した収納だけの情報が取得できる。
アイテム名は含まれていないので、 アイテムのIDでアイテムを特定する必要がある。
データの構造はドキュメントを参照
github.com
その他、アイテムの移動や装備を行えるAPIが用意されています。
windower.ffxi.get_item(bag, slot, count)
windower.ffxi.put_item(bag, slot, count)
windower.ffxi.set_equip(inv_id, slot, bag)

スロット

slotと呼ばれている値があります。(前述のset_equipではinv_idが収納のスロット、slotが装備部位としてのスロットなので注意)
この値はアイテムが収納のどの位置にあるかを示す値になります。
各収納毎に最大80個の収納の場合、1~80のどこかに割り当てられます。
この値はゲーム内で収納のメニューを開いた際のアイテムの位置とは異なり、内部的に管理されている値です。
ゲーム内の”せいとん”や"手動並べ替え"で同じ収納内でアイテムを順番が変わったとしてもslotの値は変化しないようです。
(ただし、スタック可能なアイテムで収納内に11個と1個あった場合、'せいとん'をすることで12個にまとまり、slotに1つ空きができるようです)
別の収納からアイテムを移動した際にそのアイテムに割り当てられ、そのアイテムを移動しない限り変化しないようです。f:id:yyoshisaur:20211025143535j:plain

オーグメント

オーグメントやエンチャンの情報はextdataという要素に入っています。そのままではバイナリデータなので、libs/extdata.luaを使ってデコードします。

--  サンプル
require('logger')
extdata = require('extdata')

item = 'ルディアノスマント' -- ナイトのアンバスマント
bag_id = 9 -- safe 2

id = res.items:with('name', item).id

found_items = {}
found_items_augment = {}
for _, i in ipairs(windower.ffxi.get_items(bag_id)) do
        if i.id ==  id then
            found_items[i.slot] = i
            found_items_augment[i.slot] = extdata.decode(i)
        end
end
table.vprint(found_items)
table.vprint(found_items_augment)

f:id:yyoshisaur:20211026152335j:plainf:id:yyoshisaur:20211026152346j:plain

エンチャントの場合は、残り使用回数や再使用時間、装備ディレイが取得できます。

enchant_item = extdata.decode(item)
recast = enchant_item.next_use_time + 18000 - os.time()
equip_delay = enchant_item.activation_time + 18000 - os.time()  -- +18000秒オフセットする

アドオンのMyHomeやDimmerで使用されているので参考にしてください。
アイテムがオーグメントかエンチャントかはtypeで判断できます。
デジョンリング
f:id:yyoshisaur:20211026152355j:plain
デジョンカジェル
f:id:yyoshisaur:20211026152404j:plain

遊んでみる

調べたことを使って、何か作って遊んでみます。
題材としてアイテムの検索やスロットなどを扱えそうなので、'/lockstyleset'を作ってみます。(ゲーム内にコマンドがあるので、特に有用なことではないです)
毎回windower.ffxi.get_itemsを呼んでその都度探すと効率が悪いので、パフォーマンスを気にするのであればfindAllのようにキャッシュを作っておく良いように思います。

-- サンプル
res = require('resources')
packets = require('packets')

slots = {
    main = 0,
    sub = 1,
    range = 2,
    ammo = 3,
    head = 4,
    body = 5,
    hands = 6,
    legs = 7,
    feet = 8,
    neck = 9,
    waist = 10,
    left_ear = 11,
    right_ear = 12,
    left_ring = 13,
    right_ring =14,
    back = 15,
}

-- gearswapっぽい感じで設定できるようにする
lock_style_set = {
    -- head = {name='ほげほげ'},
    body = 'ギーヴダブレット',
    -- hands = 'ぴよぴよ',
    legs = 'ギーヴトラウザ',
    -- feet = 'ふがふが',
}

ls_set = {}

-- アイテム名からIDを見つける
for s, i in pairs(lock_style_set) do
    if type(i) == 'table' and type(i.name) == 'string' then
        ls_set[slots[s]] = {id = res.items:with('name', i.name).id}
    else
        ls_set[slots[s]] = {id = res.items:with('name', i).id}
    end
end

-- ロックスタイルはすべての収納を対象に探す
for s, i in pairs(ls_set) do
    for bag_id in pairs(res.bags) do
        if not ls_set[s].slot and not ls_set[s].bag then 
            for _, item_in_bag in ipairs(windower.ffxi.get_items(bag_id)) do
                if item_in_bag.id ==  i.id then
                    ls_set[s].slot = item_in_bag.slot
                    ls_set[s].bag = bag_id
                    break
                end
            end
        end
    end
end

p = packets.new('outgoing', 0x053)
p['Type'] = 3
p['Count'] = 0
for s, i in pairs(ls_set) do
    p['Count'] = p['Count'] + 1
    p['Inventory Index '..p['Count']] = i.slot
    p['Equipment Slot '..p['Count']] = s
    p['Bag '..p['Count']] = i.bag
    p['Item '..p['Count']] = i.id
end

packets.inject(p)