CorradeでSecondLife/OpenSimulatorのボット入門（その２：LSLで動作させてみる）

[CorradeでSecondLife/OpenSimulatorのボット入門(その１)](https://qiita.com/makopo/items/a59042c544467869707e)の続きです。前回はボットが棒立ちで終わってしまいましたが、今回はLSLを使って、実際にボットアバターを動かしてみます。[Corradeサイト](http://grimore.org/secondlife/scripted_agents/corrade)が結構癖のある造りとなっていますので、この記事で、どこに何があるのか、スクリプトを作成する際に見るべきポイントも紹介します。

## 概要

スクリプトを使ってボットアバターを動かす仕組みは、下の図のようになっています。

![relationship.png](https://qiita-image-store.s3.amazonaws.com/0/10290/c2af0933-01f4-e230-83db-a4eaf635b5a0.png)


世間一般のボットは、ボットサーバーのようなものにRESTコマンドを発行するイメージが定着していますが、Corradeは少し異なります。

Corradeボットの実体は、機能を付加して軽量にした SecondLife **クライアント**です。デフォルトでは、サーバーとして動作しているわけではありません。ボットアバターへの命令は、仮想空間のオブジェクトから**インスタントメッセージ(IM)を送信する**ことで行います。

> [設定により、CorradeがHTTP(S)で直接命令を受け付けることもできます](http://grimore.org/secondlife/scripted_agents/corrade/tutorials/integrated_web-server)。今回の記事は、入門のため、詳細は割愛します。

では、ボットアバターからの返答や、状態が変わった時の通知はどうするのでしょう？逆方向、つまり[ボットアバターからオブジェクトへのIM送信はできません](http://wiki.secondlife.com/wiki/LlInstantMessage#Notes)。このため、代わりに、[LSLを使って簡易HTTPサーバーを立て](http://wiki.secondlife.com/wiki/LlRequestURL)、そこへボットアバターからPOSTしてもらうようにします。

## スクリプトを書いてみる

それでは早速Corradeでボットに命令をしてみましょう。と思ってCorradeのサイトを見ても、どこに何が書いてあるか途方に暮れるかもしれません。私も途方に暮れました。実は、[Corrade - Scripting Consideration](http://grimore.org/secondlife/scripted_agents/corrade/scripting_considerations) が事実上のプログラミングガイドのようなものになっています。

そんなものはいいから、動くものを触りたい！という場合は、[Second Life Marketplace の CORRADE ショップ](https://marketplace.secondlife.com/stores/165275)から0$で購入するという方法があります。ほぼ全てHUD(仮想空間には表示されず、画面インターフェースに表示されるオブジェクト)となっていますので、とりあえず装着してクリックしてみれば、何か動きます。HUDの中にあるスクリプトを開いて、本記事を参考に、処理の流れを追っていくと良いでしょう。

今回は、クリックしたらボットアバターがその場に座り、もう一度クリックしたら立ち上がる、というHUDを作ってみます。

### HUD開発環境の準備

1. オブジェクトが置ける場所にテレポートします。置ける場所が思い浮かばない方は、[MeltingdotsのSandbox](http://maps.secondlife.com/secondlife/MeltingDots/15/18/22)あたりをお薦めしておきます。
1. `Ctrl+4`を押し、適当に地面をクリックして、オブジェクトを新規作成します。名前は適当で良いです。
1. オブジェクトを`Take`または`取る`します。
1. インベントリを開き、拾ったオブジェクトを探します。「最新」というタブで探すと探しやすいです。
1. オブジェクトを右クリック->`HUD装着先`->`左下`を選択し、オブジェクトを画面に表示させます。
1. 装着したオブジェクトを右クリック->`編集`で編集ダイアログを開き、`中身`タブをクリックします。
1. `新しいスクリプト`ボタンをクリックします。
1. `新規スクリプト`をダブルクリックすると、エディタらしきものが開きます。
1. この状態で、ボットのある場所までテレポートして戻ります。

### 枠を作る

クリックで状態を切り替える仕組みを作っておきます。開いているエディタの中のスクリプトを、以下のコードで上書きします。

```phase1.lsl
default
{
	touch_start(integer num_detected)
	{
		llOwnerSay("ボットアバターを座らせます");
		//
		// ここで[地面に座る]を命令
		//
		state on_the_ground;
	}
}

state on_the_ground
{
	touch_start(integer num_detected)
	{
		llOwnerSay("ボットアバターを立たせます");
		//
		// ここで[立ち上がる]を命令
		//
		state default;
	}
}
```

保存した後、オブジェクトをクリックして、「座らせます」「立たせます」がチャットに交互に表示されていればOKです。

ここまで書いたスクリプトのGistは[こちら](https://gist.github.com/Makopo/2db922b7f653b2796199794492c89f8b)です。

### APIの入手

次に、やりたいことができるような関数を探してみましょう。

API一覧は、こちらにあります。
http://grimore.org/secondlife/scripted_agents/corrade/api/progressive

APIは、3つのカテゴリに分類されています。

* [COMMANDS](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/commands) ... ボットに命令する関数
* [CONFIGUFATION](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/configuration) ... ボットの設定プロパティ
* [NOTIFICATIONS](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/notifications) ... ボットからのイベント通知のPOSTリクエスト仕様

今回出すべき命令は、「その場に座る(ground sit)」と「立ち上がる(stand)」の2つですので、COMMANDSの関数の中から、それっぽいものを探します。後者は比較的すぐ見つかると思いますが、前者は「ground sit site:grimore.org」のような感じでGoogle検索しないと見つからないかもしれません…

どうやら、以下の関数が、それっぽいことをするようです。

* [地面に座る(RELAX)](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/commands/relax)
* [立ち上がる(STAND)](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/commands/stand)

#### 地面に座る(RELAX)

[API仕様](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/commands/relax)を確認します。リンク先の画面の中に、`Example`というコーナーがあり、そこにスクリプトが書いてあります。これをそのまま、書きかけのスクリプトの`ここで[地面に座る]を命令`の位置に追加します。

![relax.png](https://qiita-image-store.s3.amazonaws.com/0/10290/d6952191-5317-8a61-cfa9-ecbb230fc5eb.png)

```example.lsl
llInstantMessage(CORRADE,
    wasKeyValueEncode(
        [
            "command", "relax",
            "group", wasURLEscape(GROUP),
            "password", wasURLEscape(PASSWORD)
        ]
    )
);
```

よく見ると、`wasKeyValueEncode`や`wasURLEscape`という関数が使われています。これは、Corradeの「ヘルパー関数」というものです。後で入手方法を説明します。

`CORRADE`、`GROUP`、`PASSWORD`定数に設定すべき内容も、後で説明します。

#### 立ち上がる(STAND)

同様に、[API仕様](http://grimore.org/secondlife/scripted_agents/corrade/api/progressive/commands/stand)を確認します。`Example`のスクリプトを`ここで[立ち上がる]を命令`の場所に追加します。

```example.lsl
llInstantMessage(CORRADE,
    wasKeyValueEncode(
        [
            "command", "stand",
            "group", wasURLEscape(GROUP),
            "password", wasURLEscape(PASSWORD)
        ]
    )
);
```

ここまで書いたスクリプトのGistは[こちら](https://gist.github.com/Makopo/43471af47f7c5433e059cb85c4c9b2b4)です。

### 定数を追加

先に、`CORRADE`、`GROUP`、`PASSWORD`を解決します。書きかけのスクリプトの先頭に、以下を追加します。`XXXX`とか`YYYY`とかはダミーです。

```header.lsl
string  CORRADE  = "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
string  GROUP    = "My Group";
string  PASSWORD = "YYYYYYYY";
```

`CORRADE`には、ボットアバターのUUIDが入ります。

#### CORRADE(ボットアバターのUUID)

オブジェクトの中に新しいスクリプトを作成して、以下で上書きして保存します。

```uuid_detector.lsl
default
{
    touch_start(integer num_detected)
    {
         llSensor("", NULL_KEY, AGENT_BY_LEGACY_NAME, 5.0, PI);
    }
 
    sensor (integer num_detected)
    {
        integer i = 0;
        while (i < num_detected)
        {
            llOwnerSay(llDetectedName(i) + ", " + (string)llDetectedKey(i));
            i++;
        }
        llSensorRemove();
    }
 }
```

ボットアバターに近づいた状態でオブジェクトをクリックすると、「座らせます」「立たせます」以外に、名前とUUIDの組がチャットに表示されます。

確認し終わったら、新しく作成した方のスクリプトはオブジェクトから消しておきましょう。

#### GROUPとPASSWORD

[その１で設定した](https://camo.qiitausercontent.com/167ad4534989e20df28bafcd2e56624bdee49739/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f31303239302f39356266323239302d636362332d306533362d376564622d6236306166653834306562362e706e67)内容をここで設定します。
`GROUP`は、特に変更していない限り`My Group`です。
`PASSWORD`は、`Group Password`で設定した内容とします。

ここまで書いたスクリプトのGistは[こちら](https://gist.github.com/Makopo/1ab0906f02f353d5274dad0951b79c0c)です。

### ヘルパー関数の入手

最後に、`wasKeyValueEncode`や`wasURLEscape`を解決します。

CorradeのLSLヘルパー関数は、ここにあります。URLやタイトルからは想像できませんが…
http://grimore.org/fuss/data_structures/key-value_pairs#index

1箇所で全て入手できるわけではなく、関数ごとにページが分かれています。主な関数のみ転載します。

|  関数名                 |  説明                       |
|:-----------------------|:---------------------------|
| [wasKeyValueEncode()](http://grimore.org/fuss/data_structures/key-value_pairs/encode)    | listからkey-value文字列を生成 |
| [wasKeyValueURLEscape(), wasURLEscape() ](http://grimore.org/fuss/data_structures/key-value_pairs/rfc1738/to) | RFC1738(URL)文字列へエスケープ  |
| [wasKeyValueDecode()](http://grimore.org/fuss/data_structures/key-value_pairs/decode)    | key-value文字列からlistを生成 |
| [wasKeyValueURLUnescape(), wasURLUnescape()](http://grimore.org/fuss/data_structures/key-value_pairs/rfc1738/from) | RFC1738(URL)文字列のエスケープ解除 |
| [wasKeyValueGet()](http://grimore.org/fuss/data_structures/key-value_pairs/get)       | 特定のプロパティのvalueを取得  |
| [wasKeyValueSet()](http://grimore.org/fuss/data_structures/key-value_pairs/set)       | 特定のプロパティの値を変更したkey-value文字列の取得 |
| [wasKeyValueDelete()](http://grimore.org/fuss/data_structures/key-value_pairs/delete)    | 特定のプロパティを抜いたkey-value文字列を取得 | 

`wasKeyValueEncode`と`wasURLEscape`のページを開いて、「LSL」セクションに書いてあるコードをコピーして、定数より下、`default`の行より上に貼ります。関数ヘッダのGPLv3宣言も含めて貼りましょう。

これで保存すると、正常に保存が完了するはずです。

完成したスクリプトのGistは[こちら](https://gist.github.com/Makopo/23e8b9a872b77f929d70ba70f42b2d0e)です。

## 動かしてみる

オブジェクトをクリックします。うまくいけば、ボットアバターがその場で胡座をかいて座るはずです。

![sit.png](https://qiita-image-store.s3.amazonaws.com/0/10290/f40bb63f-1e77-89b5-e63f-dade2a1bbdd7.png)

もう一度オブジェクトをクリックすると、立ち上がるはずです。

![stand.png](https://qiita-image-store.s3.amazonaws.com/0/10290/3aaf4594-4463-cec7-645d-5bbd4a154c3c.png)

Corradeのコマンドプロンプトやログに、`relax`や`stand`の命令がされた旨が出力されます。

![log.png](https://qiita-image-store.s3.amazonaws.com/0/10290/3b22a415-5c1f-2f20-9e57-daac1c3f70df.png)

## サンプルなど

ここまでで、一通り雰囲気はつかめたかと思います。興味があれば、もう少し複雑なサンプルを見てみましょう。[Various Corrade Projects](http://grimore.org/secondlife/scripted_agents/corrade/projects/start)が、事実上、サンプルの寄せ集めページになっています。

アドベントカレンダーでのCorrade入門記事は、この記事で最後です。今後、続編を書くことがあれば、Corradeタグで通常記事として書くつもりです。