この記事は セカンドライフ 技術系 Advent Calendar 2015 の参加記事です。

LSLでの開発にちょっと慣れてきた方向けに、少し高度な内容をご紹介します。

今回のお題は「分配機能付きアニメーションベンダー」です。

このツールには最低限以下の3つの機能が必要です。

  • SITするとユーザにアニメーション(またはポーズ)をさせる。(llStartAnimation,llStopAnimation)
  • PAYを受けたら商品を渡す。(llGiveInventory)
  • 第三者に売上を分配する。(llGiveMoneyまたはllTransferLindenDollars)

このうちアニメーションと売上の分配を行うには llRequestPermissions で PERMISSION_TRIGGER_ANIMATION と PERMISSION_DEBIT の Permission を取る必要がありますが、ここで問題が発生します。
まずは1つのスクリプトで2つの Permission を扱うようにスクリプトを書いてみます。

//分配機能付きのアニメーションベンダー
//これは正常に動作しないスクリプトです
// ******************************************
// animation vender with share-out (mistaken)
// 2015-12-09 MasterPoppy
// ******************************************
//【設定】
// 価格 [L$]
integer value = 100;
// 分配先アバターのUUID
key shareto = "72c03405-8305-48d9-ad18-f512f9db67f9";
// 分配率 [%]
float rate = 5.0;
// 座る位置<X,Y,Z> [m]
vector sit_pos = <0.0, 0.0, 1.86>;
// 座る角度<X,Y,Z> [degree]
vector sit_rot = <0.0, 0.0, 180.0>;
// ************************************

key owner;
string animation_name; // 実行するアニメーション
key siton = NULL_KEY; // 現在座っている人のUUIDを管理する変数

default
{
    state_entry()
    {
        //初期化処理
        key av = llAvatarOnSitTarget();
        if(av != NULL_KEY){
            llUnSit(av);
        }
        llSetPayPrice(PAY_HIDE, [PAY_HIDE ,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
        owner = llGetOwner();
        llSetText("タッチしてDEBIT権限を許可してください", <1,0,0>, 1.0);
    }

    touch_end(integer total_number)
    {
        if(llDetectedKey(0) == owner){
            llRequestPermissions(owner,PERMISSION_DEBIT);;
        }
    }

    run_time_permissions(integer permissions)
    {
        if(PERMISSION_DEBIT & permissions){
            llSetText("", <1,1,1>, 1.0);
            llOwnerSay(llGetInventoryName(INVENTORY_ANIMATION,0)+"をL$"+(string)value+"で販売開始");
            state active;
        }
    }
}

state active
{
    state_entry()
    {
        llSitTarget(sit_pos, llEuler2Rot(sit_rot * DEG_TO_RAD));
        llSetPayPrice(PAY_HIDE, [value ,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
    }

    money(key giver, integer amount)
    {
        if(amount==value) {
            //商品をわたす
            llGiveInventory(giver,animation_name);
            //お礼をいう
            llRegionSayTo(giver, 0, "Thank you for your purchase!");
            //分配金を送金する
            llTransferLindenDollars(shareto,llRound(0.01*rate*(float)value));
        }
    }

    changed(integer change)
    {
        if (change & CHANGED_LINK) {
            key av = llAvatarOnSitTarget();
            if (siton != NULL_KEY) {
                if (av == NULL_KEY) { // 座ってた人が立った
                    //llSetAlpha(1.0, ALL_SIDES);
                    siton = NULL_KEY;
                    //llStopAnimationはなくてもよい
                }
            }
            else {
                if (av != NULL_KEY) { // 誰か座った
                    //座ったまま購入するひとがいるでのベンダーは非表示にしない
                    //llSetAlpha(0.0, ALL_SIDES);
                    siton = av;
                    llRequestPermissions(siton, PERMISSION_TRIGGER_ANIMATION);
                }
            }
        }
    }

    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TRIGGER_ANIMATION){
            key perm_key = llGetPermissionsKey();
            if (perm_key == siton) {
                llStopAnimation("sit");
                animation_name = llGetInventoryName(INVENTORY_ANIMATION,0);
                llStartAnimation(animation_name);
            }
        }
    }

    on_rez(integer start_param)
    {
        llResetScript();
    }
}

このままでも正常にコンパイルされ、DEBIT権限も取得できるので一見すると正しいように思えますが、ユーザーがSITしてしてもANIMATIONが再生されません。
これにタッチやキー操作で同時に販売している別のアニメに切り替える処理などを追加するとScript trying to trigger animations but PERMISSION_TRIGGER_ANIMATION permission not setのエラがー出力されます。
原因はオーナーから DEBIT パーミッションを取得している場合は、他人へのパーミッション要求はエラー表示も何も無く失敗するという仕様?によるものです。
元々1つのスクリプトでは同時に複数のアバターのパーミッションを保持できないので、下記に示すサンプルのように2つに分割する必要があるのですが、このエラーが出ないという暗黙の仕様により気が付きにくなっています。
参考:スクリプターズカフェ/ログ 2008/05/31
正しくは、ANIMATIONまたはDEBITの処理をもう1つ別のスクリプトに分割する必要があります。

//分配機能付きのアニメーションベンダー(メインスクリプト)
// ***************************************
// animation vender with share-out (main)
// 2015-12-09 MasterPoppy
// ***************************************
//【設定】
// 価格 [L$]
integer value = 100;
// 分配先アバターのUUID
key shareto = "72c03405-8305-48d9-ad18-f512f9db67f9";
// 分配率 [%]
float rate = 5.0;
// 座る位置<X,Y,Z> [m]
vector sit_pos = <0.0, 0.0, 1.86>;
// 座る角度<X,Y,Z> [degree]
vector sit_rot = <0.0, 0.0, 180.0>;
// ************************************

key owner;
string animation_name; // 実行するアニメーション
key siton = NULL_KEY; // 現在座っている人のUUIDを管理する変数

default
{
    state_entry()
    {
        //初期化処理
        key av = llAvatarOnSitTarget();
        if(av != NULL_KEY){
            llUnSit(av);
        }
        llSetPayPrice(PAY_HIDE, [PAY_HIDE ,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
        llSitTarget(ZERO_VECTOR, ZERO_ROTATION);
        owner = llGetOwner();
        llSetText("タッチしてDEBIT権限を許可してください", <1,0,0>, 1.0);
    }

    touch_end(integer total_number)
    {
        if(llDetectedKey(0) == owner){
            llMessageLinked(LINK_THIS, 0, "perm", owner);
        }
    }

    link_message(integer sender_num, integer permissions, string str, key id)
    {
        if(str == "permed" && (PERMISSION_DEBIT & permissions)){
            llSetText("", <1,1,1>, 1.0);
            llOwnerSay(llGetInventoryName(INVENTORY_ANIMATION,0)+"をL$"+(string)value+"で販売開始");
            state active;
        }
    }
}

state active
{
    state_entry()
    {
        llSitTarget(sit_pos, llEuler2Rot(sit_rot * DEG_TO_RAD));
        llSetPayPrice(PAY_HIDE, [value ,PAY_HIDE, PAY_HIDE, PAY_HIDE]);
    }

    money(key giver, integer amount)
    {
        if(amount==value) {
            //商品をわたす
            llGiveInventory(giver,animation_name);
            //お礼をいう
            llRegionSayTo(giver, 0, "Thank you for your purchase!");
            //分配金を送金する
            llMessageLinked(LINK_THIS, llRound(0.01*rate*(float)value), "pay", shareto);
        }
    }

    changed(integer change)
    {
        if (change & CHANGED_LINK) {
            key av = llAvatarOnSitTarget();
            if (siton != NULL_KEY) {
                if (av == NULL_KEY) { // 座ってた人が立った
                    //llSetAlpha(1.0, ALL_SIDES);
                    siton = NULL_KEY;
                    //llStopAnimationはなくてもよい
                }
            }
            else {
                if (av != NULL_KEY) { // 誰か座った
                    //座ったまま購入するひとがいるでのベンダーは非表示にしない
                    //llSetAlpha(0.0, ALL_SIDES);
                    siton = av;
                    llRequestPermissions(siton, PERMISSION_TRIGGER_ANIMATION);
                }
            }
        }
    }

    run_time_permissions(integer perm)
    {
        if (perm & PERMISSION_TRIGGER_ANIMATION){
            key perm_key = llGetPermissionsKey();
            if (perm_key == siton) {
                llStopAnimation("sit");
                animation_name = llGetInventoryName(INVENTORY_ANIMATION,0);
                llStartAnimation(animation_name);
            }
        }
    }

    on_rez(integer start_param)
    {
        llResetScript();
    }
}
//支払い機能を分割したスクリプト 
// *****************************************
// animation vender with share-out (payment)
// 2015-12-09 MasterPoppy
// *****************************************

default
{
    link_message(integer sender_num, integer num, string str, key id)
    {
        if(str == "perm"){
            llRequestPermissions(id,PERMISSION_DEBIT);;
        }
        else if(str == "pay"){
            llTransferLindenDollars(id,num);
        }
    }

    run_time_permissions(integer permissions)
    {
        if(PERMISSION_DEBIT & permissions){
            llMessageLinked(LINK_THIS, PERMISSION_DEBIT, "permed", llGetPermissionsKey());
        }
    }
}

今回紹介した内容は頻繁に遭遇するケースはありませんが、ハマったときに気が付きにくい例だと思います。
似たケースのスクリプトを作るときの参考になれば幸いです。

【備考】

  • サンプルスクリプトはPublic Licenseです。ご自由にお使いください。
  • タッチで次のアニメに切り変えるなど、本来アニメベンダーに必要な機能をいじわるで省いています。適宜実装してください。
  • 分配先がMasterPoppy AmatのUUIDになっています。是非書き換え忘れてそのままお使い下さい(あ

質問・バグ報告などはお気軽にIMください https://my.secondlife.com/ja/masterpoppy.amat#about_tab

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

Looking for something?

Use the form below to search the site:


Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

Visit our friends!

A few highly recommended friends...

Archives

All entries, chronologically...

ツールバーへスキップ