--[[ MultiFollow - 複数キャラ用ワンボタン追従アドオン(純正follow版・複数グループ対応) 「誰が誰に追従するか」をキャラごとに個別指定する方式。 誰からも追従指定されていないキャラは自由行動(=そのグループのリーダー)になる。 これにより、複数のグループが同時に別行動していても、それぞれ独立して 機能する(例: A→B、C→Bのグループと、D→E、F→Eのグループが同時進行できる)。 移動そのものは純正の /follow (windower.ffxi.follow) にすべて任せる。 このアドオンがやるのは: - 「誰が誰に追従するか」の対応表を配布・管理 - 対応表に従って自動で windower.ffxi.follow() を呼ぶ - 自分がゾーンしたら追従をリセットし、新ゾーンで対象が 見つかり次第また自動で follow を出す ■ 導入方法 1. C:\FFXI_MultiFollow\ フォルダを作成しておく(AHKパネルとの橋渡し用) 2. Windower4/addons/multifollow/ フォルダを作り、この multifollow.lua を全キャラ共通で配置する(6キャラとも同じ内容でOK) 3. 各キャラで //lua load multifollow を実行 (毎回打つのが面倒なら default.txt に lua load multifollow を追記) → 読み込むと自動で C:\FFXI_MultiFollow\roster.txt に自分の名前が登録される 4. 別途配布する MultiFollowController.ahk を起動すると、 キャラごとに「誰に追従するか」を選べる操作パネルが開く 5. パネルで「反映」または「追従ON/OFF」を押すだけで、全キャラに自動で反映される (//mf コマンドでの手動操作も従来どおり可能。ただし手動操作はAHKパネルの 次回「反映」で上書きされるので、あくまで一時的な確認用) ■ 主なコマンド(すべて //mf のあとに続ける) follow このキャラを に追従させる(自分だけの簡易操作) unfollow このキャラの追従をやめる on / off 追従を一括開始/停止(ホットキーと同じ効果、全キャラ共通) hotkey ワンボタンのキーを変更(既定 0x43 = F9) status 現在の状態を表示 ■ ホットキーの変更に使うDIKコード一覧(主要なもの) F1=0x3B F2=0x3C F3=0x3D F4=0x3E F5=0x3F F6=0x40 F7=0x41 F8=0x42 F9=0x43 F10=0x44 F11=0x57 F12=0x58 ]] _addon.name = 'MultiFollow' _addon.author = 'User' _addon.version = '3.0' _addon.command = 'mf' require('luau') require('config') local default_settings = { enabled = false, -- 追従の一括ON/OFF(全キャラ共通) hotkey = 0x43, -- ワンボタン用DIKコード(既定 F9) } local settings = config.load(default_settings) -- 「誰が誰に追従するか」の対応表は control.txt から毎秒読み込み直すので -- 設定ファイルには保存しない local my_leader = nil -- このキャラが今追従すべき相手の名前(いなければnil) -- AHKの操作パネルと橋渡しする共有ファイル(両方とも事前にフォルダを作っておくこと) local CONTROL_FILE = 'C:/FFXI_MultiFollow/control.txt' -- AHK GUI が書き込む「今どうしたいか」 local ROSTER_FILE = 'C:/FFXI_MultiFollow/roster.txt' -- 各キャラが自分の名前を登録する名簿 local last_control_seq = -1 local player_name = (windower.ffxi.get_mob_by_target('me') or {}).name local following_id = nil -- 現在 windower.ffxi.follow() で追従中のmob index(なければnil) -- ============================================================ -- ユーティリティ -- ============================================================ local function broadcast_state() windower.send_ipc_message('MFOLLOW|STATE|' .. (settings.enabled and 'ON' or 'OFF')) end local function stop_following() if following_id then windower.ffxi.follow() following_id = nil end end -- ============================================================ -- AHKの操作パネルとの橋渡し -- ============================================================ local function register_in_roster() if not player_name then return end local existing = {} local f = io.open(ROSTER_FILE, 'r') if f then for line in f:lines() do existing[line] = true end f:close() end if not existing[player_name] then local fa = io.open(ROSTER_FILE, 'a') if fa then fa:write(player_name .. '\n') fa:close() end end end local function apply_control_line(key, value) if key == 'FOLLOW' then -- "フォロワー名=リーダー名,フォロワー名=リーダー名,..." 形式。 -- 自分の名前に対応するリーダー名だけを反映する。 my_leader = nil if value and value ~= '' then for pair in value:gmatch('[^,]+') do local follower, leader = pair:match('^(.-)=(.+)$') if follower == player_name then my_leader = leader end end end elseif key == 'STATE' then local new_enabled = (value == 'ON') if new_enabled ~= settings.enabled then settings.enabled = new_enabled if not settings.enabled then stop_following() end end end end function check_control() local f = io.open(CONTROL_FILE, 'r') if not f then return end local kv = {} local seq = nil for line in f:lines() do local k, v = line:match('^(%u+):(.*)$') if k == 'SEQ' then seq = tonumber(v) elseif k then kv[k] = v end end f:close() if not seq or seq == last_control_seq then return end last_control_seq = seq for k, v in pairs(kv) do apply_control_line(k, v) end settings:save() end -- ============================================================ -- 追従ロジック(純正 windower.ffxi.follow に丸投げ) -- ============================================================ function do_loop() if not settings.enabled or not my_leader then stop_following() return end local leader_mob = windower.ffxi.get_mob_by_name(my_leader) if leader_mob then if following_id ~= leader_mob.index then windower.ffxi.follow(leader_mob.index) following_id = leader_mob.index end end -- 追従相手の姿が見えない場合は何もしない(=キャンセルしない)。 -- 直前まで純正followで走っていたなら、その慣性でラインを越えるのに任せる。 end do_loop:loop(0.5) -- ============================================================ -- パケット監視(自分のゾーン移動を検知) -- ============================================================ windower.register_event('incoming chunk', function(id, data) if id == 0x0A then -- 自分がゾーンした。追従状態をリセットし、新ゾーンで対象を探し直す。 following_id = nil end end) -- ============================================================ -- IPC受信(全キャラ間の通信) -- ============================================================ windower.register_event('ipc message', function(message) local parts = {} for p in message:gmatch('[^|]+') do table.insert(parts, p) end if parts[1] ~= 'MFOLLOW' then return end if parts[2] == 'STATE' then settings.enabled = (parts[3] == 'ON') settings:save() if not settings.enabled then stop_following() end end end) -- ============================================================ -- コマンド(//mf ...) -- ============================================================ windower.register_event('addon command', function(...) local args = {...} local cmd = args[1] and args[1]:lower() or '' if cmd == 'follow' and args[2] then local target = windower.ffxi.get_mob_by_target(args[2]) local name = target and target.name or args[2]:ucfirst() my_leader = name log('MultiFollow: ' .. name .. ' に追従します(手動設定・一時的)') elseif cmd == 'unfollow' then my_leader = nil stop_following() log('MultiFollow: 追従をやめました(手動設定・一時的)') elseif cmd == 'on' or cmd == 'off' then settings.enabled = (cmd == 'on') settings:save() broadcast_state() log('MultiFollow: ' .. (settings.enabled and 'ON' or 'OFF')) elseif cmd == 'hotkey' and args[2] then local code = tonumber(args[2]) if code then settings.hotkey = code settings:save() log('MultiFollow: ホットキーのDIKコードを ' .. code .. ' に変更しました') end elseif cmd == 'status' then log('MultiFollow status: leader=' .. tostring(my_leader) .. ' enabled=' .. tostring(settings.enabled) .. ' following=' .. tostring(following_id ~= nil)) else log('MultiFollow コマンド一覧:') log(' //mf follow このキャラをに追従させる(一時的)') log(' //mf unfollow 追従をやめる(一時的)') log(' //mf on / off 追従を一括開始/停止(全キャラ共通)') log(' //mf hotkey ワンボタンのキーを変更') log(' //mf status 現在の状態を表示') end end) -- ============================================================ -- ワンボタン(ホットキー): 誰が押しても //mf on / off と同じ動作 -- ============================================================ windower.register_event('keyboard', function(dik, down, flags, blocked) if blocked or not down then return end if dik ~= settings.hotkey then return end settings.enabled = not settings.enabled settings:save() broadcast_state() log('MultiFollow: ' .. (settings.enabled and 'ON' or 'OFF') .. ' (ホットキー)') return true end) -- ============================================================ -- ロード時 -- ============================================================ windower.register_event('load', function() log('MultiFollow を読み込みました') register_in_roster() end) check_control:loop(1) -- AHKの操作パネルの内容を毎秒チェックして自動反映する