FC2ブログ

    Lua

    Logicool Gaming Softwareでスクリプトを書こう

    Logicool Gaming Software(以下LGS)さん実はLuaスクリプトを書けます。 

    その前に、LGSのマクロのお話を少し。

    LGSには一定の操作を簡単に自動化できるマクロ機能が実装されています。

    マクロを使うと、「コピー(Ctrl+C)」をワンクリックでできたり、するわけですね。

    ただ「コピー」するだけならショートカット押せばいいじゃないかと思われますので、パッと思いついた使いみちを書くと。
    例えば、「AとDを交互に連打したい」場合があったとすると、通常ならキーボードで「ADADADAD・・・」と打たなければならないわけですが、マクロを使うと「AD」を「1秒ごとに繰り返す」ということができるわけですね。
    (DbDで使っている人がいるかも知れない)
    この操作を、LGSのマクロ機能で書くと画像のようになります。

    s-CB_0006.jpg

    ちょっと説明
    ○キーストローク
    ・下矢印がキーを押し込む操作
    ・上矢印がキーを離す操作
    ・X秒の遅延はX秒何も操作しない時間

    ○繰り返しオプション
    ・ボタンを押している間、1秒ごとにキーストロークを繰り返す

    ●LGSマクロの長所・短所
    ○長所
    ・お手軽
    「記録を開始」ボタンを押したあとに、再現したいキー入力を押すだけでマクロが作れる

    ○短所
    ・可読性が悪い
    上矢印のキーがどこで押されていて、どこで離されているのか分かりづらい。
    ・編集性が悪い
    「AD」と入力していたけれど、「ACD」としたいと思ったときには、「AD」の操作の間を選択して「ここより前/後から記録」としてキー入力を足すわけですが、複雑なマクロをトライアル・アンド・エラーしようと思うと可読性の悪さと相まって、もはや新規にマクロを作り直すことになります。
    ・繰り返しに弱い
    「ABCDEF」「Q」「ABCDEF」「Z」と入力したい場合、素直に「ABCDEF」「Q」「ABCDEF」「Z」とマクロを組むしかない。
    何当たり前のことを言っているんだと思われるかもしれませんが、プログラミングをしたことのある人なら「ABCDEF」の入力は「関数にしてやればいい」とか「for」で回せばいいとか思うことでしょう。

    ●マクロとスクリプトの比較
    イマイチ長所と短所が伝わりづらいと思いますので、「ABCDEF」「Q」「ABCDEF」「Z」をマクロとスクリプトで組んだ場合の違いをお伝えします。

    ○マクロ
    左から右にキー入力のマクロが進んでいきます、記録した直後に見ても、もはや何をしているのかよくわからない状態になっています。
    (マクロ画像はまだ続きますが、ここまでにしておきます。)
    s-CB_0007.jpg s-CB_0008.jpg


    ○スクリプト
    キーの入力(押し込みと離す操作)を1行で書けるので、マクロと比べると非常にスッキリしているかと思います。
    s-CB_0011.jpg 

    PressAndReleaseKey()は「キーを押して離す」操作をしています。
    例えば、PressAndReleaseKey("a")ならAキーを押して、離すということですね。
    PressAndReleaseKey("a", "b")だと、Aキーを押して離したあとに、Bキーを押して離すといった具合になります。

    ○「ABCDEF」「Q」「ABCDEF」「Z」から「ABCDEF」「Q13」「ABC2DEF」「Z3」に変えたい
    マクロの場合は、「Qの後から記録」で「13」をいれて・・・とできないこともないですが、非常に分かりづらいです。

    一応編集したマクロをペタリ。
    s-CB_0013.jpg 

    「Qを離して」から「1を押す」までに遅延が入っていませんね・・・とか、「Q13」としたつもりが「ABCDEF13」になっていたということが容易に起こりうる可読性の悪さです。

    スクリプトをペタリ
    s-CB_0014.jpg 

    非常にわかりやすいですね。
    また、テキストなので、編集が非常に簡単です。
    マクロで、「ああでもないこうでもない」「間違えた・・・」「なんじゃこりゃー」となるところを、スクリプトなら数行でしかも読みやすく、編集しやすくかけるわけです。

    これはもうスクリプトを使うしかない!

    ということで、スクリプトを書きたい方は、先人が書いている解説を参考に学んでみましょー!
    (丸投げ)

    参考:

    自分用に作った関数たち
    昨日スクリプト機能に気がついて書いたのでまだまだ編集中


    スポンサーサイト



    PAYDAY2 Luaいろいろ

    既存のMODの改変など個々に記事にするほどでもない雑多なものをまとめてここに書きます。

    一番下に開発環境や開発までの流れを書いていますので、いじりたい人は参考にしてね。

    ※全て自己責任で行ってください。変更前にバックアップを取ることをお勧めします。


    既存のMODの改変


    JackHUDのアイコン(hudlists)の大きさを変える


    20160320165115_1.jpg
    20160417051257_1.jpg

    ファイル名:HUDManagerPD2_ext.lua
    パス:\SteamLibrary\SteamApps\common\PAYDAY 2\mods\JackHUD\Lua\HUDManagerPD2_ext.lua
    変更箇所:257行目辺りの数字を適当に書き換える。(整数以外も設定可能)
    right_list_scale = 1, --Size scale of right list
    left_list_scale = 1, --Size scale of left list
    buff_list_scale = 1, --Size scale of buff list

    JackHUDのインタラクトホールド機能発動までの時間を設定する


    インタラクトは対象ごとにいくつかに分かれている。ここではBagやAssetsのインタラクトホールド機能を対象とする。

    ファイル名:PlayerStandard_ext.lua
    パス:\SteamLibrary\SteamApps\common\PAYDAY 2\mods\JackHUD\Lua\PlayerStandard_ext.lua
    変更箇所:
    ホールド機能発動までの時間: 18行目辺りの数字を適当に書き換える、もしくは新たに変数を定義する。(整数以外も設定可能)
    local TIMEOUT = 0.5

    ホールド機能発動までの時間を処理するコード:
    変更前:
    function PlayerStandard:_check_action_interact(t, input, ...)
    if not (self:_check_interact_toggle(t, input) and JackHUD:GetOption("push_to_interact")) then
    return _check_action_interact_original(self, t, input, ...)
    end
    end


    変更後
    function PlayerStandard:_check_action_interact(t, input, ...) 
    local is_locked = self._interact_expire_t and (t - (self._interact_expire_t - self._interact_params.timer) >= TIMEOUT)
    if not (self:_check_interact_toggle(t, input) and JackHUD:GetOption("push_to_interact") and is_locked ) then
    return _check_action_interact_original(self, t, input, ...)
    end
    end


    参考:PayDay 2:Custom PlayerMaskOff state and more - UnKnoWnCheaTs Game Hacking Wiki
    https://www.unknowncheats.me/wiki/PayDay_2:Custom_PlayerMaskOff_state_and_more

    参考:lastbullet - Press-2-Hold Standalone 2.1
    http://forums.lastbullet.net/mydownloads.php?action=view_down&did=13489


    開発環境


    Lua実行環境


    The Programming Language Lua http://www.lua.org/home.html
    Lua Builds http://joedf.users.sourceforge.net/luabuilds/
    ただし、PAYDAY2のMODの動作確認のためにはPAYDAY2に読み込ませるしかない。

    エディタ


    Visual Studio Code https://www.visualstudio.com/ja-jp/products/code-vs.aspx

    ファイル内文字列検索など


    Gow http://www.forest.impress.co.jp/docs/review/20111206_496043.html
    grepコマンドが便利
    例えば、hudlistを管理しているコードがどのファイルにあるのか、interactに関係しているコードがどこにあるのかなどを知りたい時には
    grep.jpg

    ファイル内文字列比較


    WinMerge http://www.geocities.co.jp/SiliconValley-SanJose/8165/winmerge.html


    Luaリファレンス


    自作MODや既存のMODをいじる際に参考にしたサイトを列挙しておきます。
    Lua リファレンス 入門 などで検索するといくらでも出てきます。

    Lua入門講座


    http://starcode.web.fc2.com/

    Lua 5.2 リファレンスマニュアル


    http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html


    MODコード、MOD配布サイト


    lastbullet BLTカテゴリ


    http://forums.lastbullet.net/mydownloads.php?action=browse_cat&cid=4

    PAYDAY2 BLT


    http://paydaymods.com/

    UnKnoWnCheaTs Game Hacking Wiki PAYDAY2カテゴリ


    https://www.unknowncheats.me/wiki/PAYDAY_2


    ソースコードや内部ID


    Decompiled source code of PAYDAY 2's Lua scripts


    https://bitbucket.org/YaPh1l/payday-2-lua

    Payday 2 BLT Documentation


    payday-2-blt-docs.readthedocs.org/en/latest/

    lastbullet wiki


    http://wiki.lastbullet.net/Main_Page

    最近の進捗

    NGBTOを参考に書いてみた。
    参考にとはいいつつもやはり手さぐりなので数時間もクラッシュと戦ってしまった。
    BLTを導入してからもう半年も経つらしいが、今更logsフォルダの存在に気が付きデバッグがとても捗った。
    SteamLibrary\SteamApps\common\PAYDAY 2\mods\logs

    このフォルダには日付別にテキストファイルでBLTのログが出力される。
    このログはシステムメッセージやチャットなどからLuaファイルの読み込みとエラーなどがすべて記録される。

    それから、クラッシュ時のログでおなじみの
    C:\Users\ユーザ名\AppData\Local\PAYDAY 2\crashlog.txt


    あとは沢山のLuaのリファレンスと沢山のMODのソースを見ながらいじる・・・。
    しかし、実行するのにいちいちPD2を立ち上げなければならない、Luaを変更して再読み込みするためにはPD2を再起動しなければならないと面倒・・・。

    理解していないけれども一応思った通りの動きをするようにはなった。

    スキルとパークとプレイ中のジョブ名あたりも記録できるようにしたい。
    教えて先生。

    このMODを入れてから
    Application has crashed: access violation
    が増えた気がするのでしばらく様子見。

    mod/PD2BLAp/lua/BaseMod.lua

    if RequiredScript == "lib/network/base/networkpeer" then
    --設定
    if not _G.PD2BLAp then
    _G.PD2BLAp = _G.PD2BLAp or {}
    PD2BLAp.Colors = {"ff0000", "00ff04", "1a64f6"} -- RGB
    PD2BLAp.settings = {
    file_path = "mods/PD2BLAp/ConnectionIDList.txt",
    header_list = "Time,Unixtime,SteamID64,Name",
    }
    end


    --ファイルの存在
    function PD2BLAp:FileExists(path)
    local fh = io.open(path, "rb")
    if fh then fh:close() end
    return fh ~= nil
    end

    --デバッグメッセージ
    function PD2BLAp:Debug_Message(message, color, message2)
    if message2 then
    managers.mission._fading_debug_output:script().log(message2, Color(color))
    end
    managers.mission._fading_debug_output:script().log(message, Color(color))
    end

    --Hooks
    Hooks:Add("NetworkManagerOnPeerAdded", "PD2BLAp:PeerAdded", function(peer, peer_id)
    --まずファイルが存在するか確認する
    file_exist_flag = PD2BLAp:FileExists(PD2BLAp.settings.file_path)

    --1行目は項目タイトル
    --1行に1 ID情報を格納
    --ファイルが存在しない場合は新しくファイルを作る
    if file_exist_flag == false then
    f = io.output(PD2BLAp.settings.file_path)
    f:write(PD2BLAp.settings.header_list.."\n")
    f:close()
    else
    f = io.open(PD2BLAp.settings.file_path , "r")
    line = f:read()
    print(line)
    --読み込んだ1行目の項目が最新のものでなければ最新のものに置き換える
    if line ~= PD2BLAp.settings.header_list and line ~= PD2BLAp.settings.header_list.."b" then
    text_all = {} --ファイルの内容

    while true do
    text = f:read()
    table.insert(text_all , text)
    if text == nul then break end
    end
    f:close()

    --書き込み内容を保持しつつ、項目タイトルを更新する
    f = io.open(PD2BLAp.settings.file_path , "w")
    f:write(PD2BLAp.settings.header_list.."\n")
    for i=1, #text_all, 1 do
    f:write(text_all[i].."\n")
    print(text_all[i])
    end
    end

    f:close()
    end

    f = io.open(PD2BLAp.settings.file_path , "a+") --追記で開きなおす

    --書き込む文字列
    write_line = os.date()..","..os.time()..","..peer:user_id()..","..peer:name()

    --PD2BLAp:Debug_Message(write_line, PD2BLAp.Colors[2])

    f:write(write_line.."\n")
    f:close()

    end)
    end

    最近の進捗

    PD2BLAの進捗

    ランキング機能は相変わらず放置中、いろいろ考えてはいるのだけれど、実装するのに必要な知識が足りないことと、拡張性を持たせて実装したいので悩み中。

    今後バックグラウンドで自動判定することを考えると、Warning判定が出たときに何か音を鳴らすとかしてもいいかもしれない。

    細かい変更だけども、AchievementDetailのバーの位置を変更して常に全ての行が見えるようにした。
    バーと表が被っているのがのがデザイン的に不恰好だっただけ。
    ついでにスクリーンショットのメニューも追加。
    手軽に絞り込みできる機能をつける予定。(UnlockTime LIKE 'XXX'のXXXを自動的に挿入するような)

    76561198147063532_AchievementDetail_20160305162153.png


    あとはフレンドを一括でチェックする機能。
    ただし、こちらはSteamとPD2StatsAPIに連続アクセスするので没になるかも。
    一応最低でも1秒以内に連続アクセスすることは無いように配慮しているが、それでいいのか?
    SteamBan情報は数百のIDに対して1度で取得できるが、AchievementとPD2Statsは1IDに対して1度のアクセスが必要。

    PD2BLA1_15_0_0_1.jpg
    (今話題のこの方!ちなみにフレンドが被害にあったので間違いなく黒。
    この人が出現するとSWATは死ぬ、残らず死ぬ。)

    PD2でユーザが接続を要求したときにバックグラウンドで勝手に判定する機能を考え中。
    プロセス間通信にはいろいろ方法があるけれど、ログを後でも見られたほうがいいかなということでテキストファイルに出力していく形で実装しようかと。
    そうなると、PD2側にMODを導入してもらわないといけなくなるが、Luaの勉強にもなるしいいかなーと、書き始めたはいいもののぜんぜん知らない言語だとはかどらないはかどらない。

    でも、思い通りに動くと楽しいね!
    実行もコマンドラインですぐ、実行速度も速いし。

    ということで、とりあえずLuaでちょろっと書いてみた。
    C#ならもっとコンパクトに書けるのに、うーん。

    ファイルを開いて、閉じて、開いて、閉じて、開く!
    すごく無駄!
    どうすれば・・・。
    (あと、Syntaxのハイライトされない)


    --http://symfoware.blog68.fc2.com/blog-entry-454.html

    --http://lua.tips/index.php?Lua%20implements_func_io
    -- 対象のfilenameのファイルが存在するかどうか。
    -- 対象のファイルのフルパス。もしくは、カレントからの相対パス。
    --戻り値bool
    function io.file_exists(path)
    local fh = io.open(path, "rb")
    if fh then fh:close() end
    return fh ~= nil
    end

    br = "n" -- 改行
    header_list = "SteamID64,Name" --出力する項目タイトル

    --まずファイルが存在するか確認する
    file_path = "ConnectedList.txt"
    file_exist_flag = io.file_exists(file_path)

    --1行目は項目タイトル
    --1行に1 ID情報を格納

    --ファイルが存在しない場合は新しくファイルを作る
    if file_exist_flag == false then
    f = io.output(file_path)
    f:write(header_list..br)
    f:close()
    else
    f = io.open(file_path , "r")
    line = f:read()
    print(line)
    --読み込んだ1行目の項目が最新のものでなければ最新のものに置き換える
    if line ~= header_list and line ~= header_list..br then
    text_all = {} --ファイルの内容

    while true do
    text = f:read()
    table.insert(text_all , text)
    if text == nul then break end
    end
    f:close()

    --書き込み内容を保持しつつ、項目タイトルを更新する
    f = io.open(file_path , "w")
    f:write(header_list..br)
    for i=1, #text_all, 1 do
    f:write(text_all[i]..br)
    print(text_all[i])
    end
    end

    f:close()
    end

    f = io.open(file_path , "a+") --追記で開きなおす
    --ID情報の書き出し処理
    f:close()