Hatena::ブログ(Diary)

ten-youの日記 RSSフィード

2009-10-12

VistaでLimeChatへのろあだいす投入法

PCの再インストールやら何やらでダイスマクロもきれいに消してしまいまして。

先日そのことを忘れてセッション開始、「あ、ダイスマクロない!」という大間抜けな事態に陥りました。

で、久々にマクロ入れようとしたらものすごい勢いで方法を忘れていたことが発覚。ので、覚えているうちにここにメモ。


1、ろあだいすをダウンロードしてくる。

DX用ダイスマクロを配布してくださってるのはRoam-Roam DX様です。

こちらの「LimeChat用DX向けダイススクリプト ろあだいす 0.10」と書かれたリンクをクリックするとマクロのzipファイルがダウンロード開始されます。

ダウンロードが終わったら適当なところに解凍

解凍すると、

  • readme.txt
  • roadice.dll
  • roadice.lmf

の3つのファイルがどこかのフォルダ内に現れると思います(一番可能性高そうなのはデスクトップ)。

2、LimeChatを起動

サーバにつなぐ必要はありません。

起動したら、メニューバーから「設定(O)」をクリックします。

f:id:ten-you:20091012225458p:image

すると「設定」メニューがいろいろ表示されるので、ここから「スクリプトの設定(C)…」を選択します。


なぜ「マクロの設定」ではなくて「スクリプトの設定」を選択するのか?

Vistaだから、です。

ダイスマクロに限らず、Vistaで起動した場合のLimeChat2は「AppData」フォルダ配下の「LimeChat2」フォルダ*1にマクロファイルを探しにいきます。だからマクロファイルは「AppData」フォルダ配下にいれておかねばなりません。

ところがインストール時のまままったくエクスプローラの設定をいじっていない場合、マクロファイルを収納する「AppData」フォルダは表示されません。つまり普通にエクスプローラでいくら探しても放り込む場所がわからないという事態に陥るのです(ていうか陥りました)。

表示の設定をいじってもいいんですが、そういうことはやりたくない場合もあると思います。そんな時にこの方法だと、たとえチャット中でもダイスマクロを所定のフォルダに放り込みにいけます。


3、「スクリプトの設定」ウィンドウを表示

f:id:ten-you:20091012225455p:image

2、の操作をすると「スクリプトの設定」ウィンドウが開くので、「スクリプトフォルダを開く(F)」ボタンをクリック。

4、エクスプローラーが表示

f:id:ten-you:20091012225449p:image

スクリプトを保存しておくフォルダが表示されます。

が、今回はマクロなので、

5、アドレスウィンドウの「LimeChat2」の右隣の▼をクリック

f:id:ten-you:20091012225447p:image

クリックすると「LimeChat2」フォルダの中に入っているフォルダ名一覧が表示されます。その中の「macros」という文字列をクリック。

6、マクロを入れるフォルダが表示。マクロファイルをコピペ

ようやく目当てのフォルダが表示できました。

というわけで、ここに1、で解凍したファイルのうち「roadice.lmf」をコピー&ペーストします。

f:id:ten-you:20091012225445p:image

↑こんな感じに。

拡張子には気をつけてください。解凍したファイルには「roadice.lmf」と「roadice.dll」の2種類があります。「macros」フォルダにペーストするのは「roadice.lmf」だけです。

7、拡張ファイルをコピペ。

次に「roadice.dll」ファイルをコピペします。これをちゃんと所定の場所にコピペしないと、マクロが動きません(←やった)。

「macros」フォルダの中は6、のとおりになっているはずです。この中の「files」フォルダをダブルクリックして開きます。

f:id:ten-you:20091012225441p:image

ここに「roadice.dll」の方をペースト。

8、マクロを有効化(その1)

さて、ここで「LimeChat2」の画面に戻ります。

「設定」メニューの中から今度は「マクロの設定」を選択します。

f:id:ten-you:20091012232613p:image

9、マクロを有効化(その2)

すると「マクロの設定」ウィンドウが開き、「macros」フォルダ内に入っているマクロファイルがすべて表示されます。

このうち「roadice」と書かれた行と、ダイスマクロを使用するサーバ名が交わっているところで右クリックします。すると以下のように○が表示されます。

f:id:ten-you:20091012232610p:image

これでマクロの有効化は終了です。お疲れ様でした。

*1:正しくは「ユーザ名\AppData\Roaming\LimeChat2\macros\

2009-03-21 ただいま現実逃避中

いぬいver.2.1

先日の結果を受けて入力部分を改変しました。ついでに先日海月歩空さまよりご指摘を受けたクリティカル値の制限も付け加え。とりあえず下限値4で。先日プレイ仲間が(あれこれ組み合わせた結果)5まで下げて見せてくれたので、一応それを下回らせてみました。

ダイス数は……DX2ndでは理論上いくつまでダイスを振る可能性があるんでしょう……あー、延々能力値伸ばしたらいくらでもあげられるか?

迷ったのでとりあえず100個にしてみました。で、ダイス数が10より多い場合は途中経過表示をしないようにもしました。この数が適当かどうかは正直謎。


追記しました(2009-3-23)

実行結果について、何を狙っての実行なのかとか全然書いてなかったので、追記しました。


スクリプト

function event::onChannelText(prefix, channel, text)
{
    /* 変数の定義 */
    //試行回数
    var sampleNumber = 600;     
    //結果格納オブジェクト定義
    var rolledResult = new Object;
    //結果返却用オブジェクト内達成値
    rolledResult.sum = 0;       //積算値
    rolledResult.max = 0;       //最大値
    rolledResult.average = 0;   //平均値

    /* 特定の入力文字列に反応する */
    if (text.match(/!\d*R\d*@*\d*/i)){
        
        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!(\d*)R(\d*)@*(\d*)/i);
        
        /* 入力された数値の検査 */
        //ダイス数
        if (matchedText[1] == ""){
            matchedText[1] = 1;
        } else if (parseInt(matchedText[1]) > 100){
            matchedText[1] = 100;
        }
        
        //クリティカル値
        if (matchedText[2] == ""){
            matchedText[2] = 10;
        } else if (parseInt(matchedText[2]) < 4){
            matchedText[2] = 4;
        }

        //繰り返し回数
        if (parseInt(matchedText[1]) > 10){
            matchedText[3] = "";
        }

        if (matchedText[3] != ""){
            if (parseInt(matchedText[3]) > 5){
                sampleNumber = 5;
            } else {
                sampleNumber = parseInt(matchedText[3]);
            }
        }

        /* 1回以上のクリティカル率をを計算 */
        //クリティカル率計算
        var rateCritical = Math.round((1 - ( Math.pow((matchedText[2]-1)/10, matchedText[1])) ) * 100);

        /* sampleNumber回振った時の平均の達成値を計算 */
        


        /* 規定回数Rダイスを振る */
        for (i=0; i < sampleNumber; i++){
            var achieve = renegadeDice(matchedText[1],matchedText[2]);
            rolledResult.sum += achieve.value;
                        
            //最大値の比較
            if (achieve.value > rolledResult.max){
                rolledResult.max = achieve.value;
            }

            //@以下に数値指定があった場合、途中経過を出力
            if (matchedText[3] != ""){
                for (j=0; j < achieve.array.length; j++){
                    send(channel, prefix.nick + ':' + achieve.array[j] );
                }
            }

        }
        
        /* 平均値を出す */
        rolledResult.average = Math.round(rolledResult.sum / sampleNumber);
    
        /* 結果をチャンネルに出力 */
        send(channel, 'いぬい「ダイス' + 
            matchedText[1] + '個、クリティカル値' + matchedText[2] +
            'で、1度以上クリティカルする確率は' + rateCritical + '%。' + 
            sampleNumber + '回振った時の達成値の平均は' + rolledResult.average + 
            '、最大値は' + rolledResult.max + '」');
    
    }
}

/* 以下は同じなので省略 */

結果

▼数字入れられるところは全部入力(ただし繰り返し回数オーバー)

20:27 (ten-you) !3r7@16

20:27 (ten-you) ten-you:3R7=[9,8,2]=10 Critical!!

20:27 (ten-you) ten-you:2R7=[7,4]=20 Critical!!

20:27 (ten-you) ten-you:1R7=[3]=23

20:27 (ten-you) ten-you:3R7=[4,2,3]=4

20:27 (ten-you) ten-you:3R7=[8,9,7]=10 Critical!!

20:27 (ten-you) ten-you:3R7=[8,2,2]=20 Critical!!

20:27 (ten-you) ten-you:1R7=[3]=23

20:27 (ten-you) ten-you:3R7=[10,7,10]=10 Critical!!

20:27 (ten-you) ten-you:3R7=[3,3,7]=20 Critical!!

20:27 (ten-you) ten-you:1R7=[8]=30 Critical!!

20:27 (ten-you) ten-you:1R7=[1]=31

20:27 (ten-you) ten-you:3R7=[9,10,6]=10 Critical!!

20:27 (ten-you) ten-you:2R7=[3,9]=20 Critical!!

20:27 (ten-you) ten-you:1R7=[2]=22

20:27 (ten-you) いぬい「ダイス3個、クリティカル値7で、1度以上クリティカルする確率は78%。5回振った時の達成値の平均は21、最大値は31」

やっぱりクリティカル値下げるのがDX2の基本なんだなとか、ちょっと実感。

▼100個より大きい数のダイス数を指定(して100個に直されたい)

20:26 (ten-you) !300r7@16

20:26 (ten-you) いぬい「ダイス100個、クリティカル値7で、1度以上クリティカルする確率は100%。600回振った時の達成値の平均は55、最大値は133」

直されてる直されてる。


ちなみに16回繰り返し振ってその結果を表示せよ、ということも一緒に指定してるのにまったく無視して600回振ってその結果を返してますが、意図してのものです。

振るダイスの数が10個を越えた時点で結果表示回数についての指定を無効化するようにしました。たとえ5回にしたって、100個のダイスの結果を延々表示するのは怖すぎる。

▼ダイス数とクリティカル値を未入力(で補完されたい)

20:31 (ten-you) !r@16

20:31 (ten-you) ten-you:1R10=[9]=9

20:31 (ten-you) ten-you:1R10=[5]=5

20:31 (ten-you) ten-you:1R10=[8]=8

20:31 (ten-you) ten-you:1R10=[4]=4

20:31 (ten-you) ten-you:1R10=[10]=10 Critical!!

20:31 (ten-you) ten-you:1R10=[2]=12

20:31 (ten-you) いぬい「ダイス1個、クリティカル値10で、1度以上クリティカルする確率は10%。5回振った時の達成値の平均は8、最大値は12」

されてるされてる。

▼クリティカル値に4より小さい数を指定(して4に直されたい)

20:34 (ten-you) !r1

20:34 (ten-you) いぬい「ダイス1個、クリティカル値4で、1度以上クリティカルする確率は70%。600回振った時の達成値の平均は25、最大値は223」

これもOK。よかったよかった。


というわけでごく単純に、入力を受け付けるファンクションで入力されてないデータを付け足したり大きすぎな数値を置き換えたりしました。それとも無理に補完しないでエラーメッセージでも出す方が親切かなあ?

2009-03-07

いぬいver.2

以前ちょろっとご披露した、「Rダイスで1回以上クリティカルする確率を返す」スクリプト『いぬい』。Rダイスのサブファンクションが出来上がった記念に(?)バージョンアップしました。

……正確には8割がた出来たかな?記念ですが。まあそれはともかく。

今度のいぬいは、600回振った時の達成値の平均値と最大値も答えます。


スクリプト

/* すくりぷと:いぬい ver.2  */

/*
nRmダイスにおいて、
1)クリティカルする確率、
2)規定回数nRD10ダイスを振った時の平均の達成値
3) 2)の達成値の最大値(最小値は0で決まってるようなもんだから)
を出力するスクリプト
*/

function event::onChannelText(prefix, channel, text)
{
    /* 変数の定義 */
    //試行回数
    var sampleNumber = 600;     
    //結果格納オブジェクト定義
    var rolledResult = new Object;
    //結果返却用オブジェクト内達成値
    rolledResult.sum = 0;       //積算値
    rolledResult.max = 0;       //最大値
    rolledResult.average = 0;   //平均値

    /* 特定の入力文字列に反応する */
    if (text.match(/!\d+R\d+/i)){
        
        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/!(\d+)R(\d+)/i);
        
        /* 1回以上のクリティカル率をを計算 */
        //クリティカル率計算
        var rateCritical = Math.round((1 - ( Math.pow((matchedText[2]-1)/10, matchedText[1])) ) * 100);

        /* sampleNumber回振った時の平均の達成値を計算 */

        /* 規定回数Rダイスを振る */
        for (i=0; i < sampleNumber; i++){
            var achieve = renegadeDice(matchedText[1],matchedText[2]);
            rolledResult.sum += achieve.value;
                        
            //最大値の比較
            if (achieve.value > rolledResult.max){
                rolledResult.max = achieve.value;
            }

/*
            //途中経過を出力
            for (j=0; j < achieve.array.length; j++){
                send(channel, prefix.nick + ':' + achieve.array[j] );
            }
*/

        }
        
        /* 平均値を出す */
        rolledResult.average = Math.round(rolledResult.sum / sampleNumber);
    
        /* 結果をチャンネルに出力 */
        send(channel, 'いぬい「ダイス' + 
            matchedText[1] + '個、クリティカル値' + matchedText[2] +
            'で、1度以上クリティカルする確率は' + rateCritical + '%」');
        send(channel, 'いぬい「' + 
            sampleNumber + '回振った時の達成値の平均は' + rolledResult.average + 
            '、最大値は' + rolledResult.max + '」');
    
    }
}

/* Rダイス */
function renegadeDice(diceNumber, critNumber)
{
    /* 前のエントリーの繰り返しなので省略 */
}

最初は最小値も出力させようと思ったのですが、よく考えてみたら600回も振れば1回はどっかでファンブルするだろうし、そうすると最小値は0固定なので省略しました。


実行結果

23:32 (ten-you) !1r9

23:32 (ten-you) いぬい「ダイス1個、クリティカル値9で、1度以上クリティカルする確率は20%」

23:32 (ten-you) いぬい「600回振った時の達成値の平均は6、最大値は38」

23:32 (ten-you) !2r9

23:32 (ten-you) いぬい「ダイス2個、クリティカル値9で、1度以上クリティカルする確率は36%」

23:32 (ten-you) いぬい「600回振った時の達成値の平均は9、最大値は43」

23:32 (ten-you) !3r10

23:32 (ten-you) いぬい「ダイス3個、クリティカル値10で、1度以上クリティカルする確率は27%」

23:32 (ten-you) いぬい「600回振った時の達成値の平均は10、最大値は29」

23:32 (ten-you) !3r7

23:32 (ten-you) いぬい「ダイス3個、クリティカル値7で、1度以上クリティカルする確率は78%」

23:32 (ten-you) いぬい「600回振った時の達成値の平均は19、最大値は72」

ちなみにスクリプトの途中にある「途中経過を出力」のコメントアウト。これを機能させると途中経過も出力してくれます。

23:33 (ten-you) !1r9

23:33 (ten-you) ten-you:1R9=[2]=2

23:33 (ten-you) ten-you:1R9=[10]=10 Critical!!

23:33 (ten-you) ten-you:1R9=[9]=20 Critical!!

23:33 (ten-you) ten-you:1R9=[6]=26

23:33 (ten-you) ten-you:1R9=[3]=3

23:33 (ten-you) ten-you:1R9=[1]=0 Fumble!!

23:33 (ten-you) ten-you:1R9=[3]=3

23:33 (ten-you) ten-you:1R9=[2]=2

23:33 (ten-you) ten-you:1R9=[4]=4

23:33 (ten-you) ten-you:1R9=[9]=10 Critical!!

23:33 (ten-you) ten-you:1R9=[6]=16

23:33 (ten-you) ten-you:1R9=[2]=2

23:33 (ten-you) ten-you:1R9=[5]=5

23:33 (ten-you) いぬい「ダイス1個、クリティカル値9で、1度以上クリティカルする確率は20%」

23:33 (ten-you) いぬい「10回振った時の達成値の平均は6、最大値は26」

……ただし、途中経過を出力する場合は

        //試行回数
        var sampleNumber = 600;     

を10回以下に減らしてください。大変なことになると思います(やってませんが)。

振ってみて気付いたこと。

600回も振(らせ)ると、それなりに出目が平均化されますね。

最大値のふり幅は大きいですが(特にクリティカル値が下がると)、平均値はいつもだいたい同じ結果が出ました。

改善点

現状のコメントアウトじゃなくて、IRCへの発言文から途中経過を出力するかどうかをコントロールできるほうが便利だなー。

ちょっと変えてみます。……また正規表現と格闘かー。

2009-03-05 恥のコテ塗り

nRcロール(3):ファンブル間違い

ファンブルだけに。

Rダイスのファンブル時の処理をいろいろクリティカルに間違ってたので、もういっそと思って新しい記事立てましたorz


  1. ループ初回に
  2. 振ったダイスのすべてが1の時、
  3. 達成値は0となる。

のがファンブル。


で、最初に書いてたコードだと、

15:42 (ten-you) #1r10

15:42 (ten-you) ten-you:1R10=[1]=1

そもそもファンブル表示してないじゃん。


じゃー達成値0にすればいいや、と以下のように書き直したわけですが、

        //一通り振ったところで結果を格納
        achieveValue += preAchieveValue;
        achieveString = achieveString.substr(0, (achieveString.length - 1))
                        + ']=' + achieveValue;
        
        //ファンブル処理
        if (preAchieveValue == 1 && count == 0){
            preAchieveValue = 0;
            achieveString += ' Fumble!!';
            criticalDice = 0;
        }

15:43 (ten-you) #1r10

15:43 (ten-you) ten-you:1R10=[1]=1 Fumble!!

やっぱり1じゃん。いや、当たり前なんですが。


ということで、以下のように書き直し。

        //一通り振ったところで結果を格納
        //ファンブル時は分岐処理
        if (preAchieveValue == 1 && count == 0){
            preAchieveValue = 0;
            achieveValue = 0;
            achieveString = achieveString.substr(0, (achieveString.length - 1))
                            + ']=' + achieveValue + ' Fumble!!';
            criticalDice = 0;
        } else {
            achieveValue += preAchieveValue;
            achieveString = achieveString.substr(0, (achieveString.length - 1))
                            + ']=' + achieveValue;
        }

実行↓

16:00 (ten-you) #2r10

16:00 (ten-you) ten-you:2R10=[1,1]=0 Fumble!!

できたできた。


この結果出すためにかなりダイス振ったのですが、やっぱりしみじみと、Rダイスは数を増やせば増やすほどファンブルしにくい、失敗しにくい判定だなと思いました。

いやたくさん失敗したいわけじゃないですが。


……たまに大失敗を買いたくなるけどね(ゲーム違い)。

2009-03-04 ひな祭りが過ぎてしまった

nR10ロール(2):戻り値をオブジェクトにしてみる

海月歩空様より頂いたコメントを参考に、というかほぼコピペで(すいません)、Rダイススクリプトの改変を試みてみました。

スクリプト本体は以下に。


スクリプト

//Rダイス呼び出し部
function event::onChannelText(prefix, channel, text)
{
    //結果返却用オブジェクト
    var achieve = new Object();
    //結果返却用オブジェクト内結果文字列用配列
    achieve.array = new Array();
    //結果返却用オブジェクト内達成値
    achieve.value = 0;
    
    if (text.match(/#\d+R\d+/ig)){

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/#(\d+)R(\d+)/i);
         
        //サブファンクションに飛ばす
        achieve = renegadeDice(matchedText[1],matchedText[2]);
        
        //結果を出力
        for (i=0; i < achieve.array.length; i++){
            send(channel, prefix.nick + ':' + achieve.array[i] );
        }

    }
}

//Rダイス計算部
function renegadeDice(diceNumber, critNumber)
{
    
    var diceRolled = 0;         //ダイス1個振った結果
    var faceDice = 10;          //ダイスの面数(DXなので10面で固定)
    var criticalDice;           //クリットしたダイスの個数をカウント
    var preAchieveValue = 0;    //プレ達成値
    var achieveValue = 0;       //達成値
    var achieveString = "";     //達成値が出るまでの過程の文字列
    var count = 0;

    //結果返却用オブジェクト
    var achieve = new Object();
    //結果返却用オブジェクト内結果文字列用配列
    achieve.array = new Array();
    //結果返却用オブジェクト内達成値
    achieve.value = 0;
    
    do {
        //変数のリセット
        criticalDice = 0;
        preAchieveValue = 0;
        achieveString =  diceNumber + 'R' + critNumber + '=[';

        //ダイス振るループ開始
        for (var i = 0; i < diceNumber; i++){
            //ダイス振る
            diceRolled = parseInt(Math.random() * faceDice)+ 1;
            
            //ダイスの出目がCRT値を上回ったかチェック
            if (diceRolled >= critNumber){
                criticalDice++;
                preAchieveValue = 10;
            }
            else if (preAchieveValue < diceRolled){
                preAchieveValue = diceRolled;
            }
            
            //振った結果のダイス目を文字列としてachieveStringに記録
            achieveString += diceRolled + ',';
        }

        //一通り振ったところで結果を格納
        achieveValue += preAchieveValue;
        achieveString = achieveString.substr(0, (achieveString.length - 1))
                        + ']=' + achieveValue;
        
        //ファンブル処理
        if (preAchieveValue == 1 && count == 0){
            achieveString += ' Fumble!!';
            criticalDice = 0;
        }
        
        //クリティカル処理
        if (criticalDice !== 0){
            achieveString += ' Critical!!';
            diceNumber = criticalDice;
        }
        
        //achieveオブジェクトの配列に達成値を出すまでの文字列を格納
        achieve.array.push(achieveString);
        count++;
        
    } while (criticalDice !== 0);
    
    achieve.value = achieveValue;
    //結果オブジェクト返却
    return(achieve);
}

すいませんほんとに言われたとおりの書きかえです。

でもすごく分かりやすかったです。ありがとうございます。

実行結果

通常。

23:03 (ten-you) #1r10

23:03 (ten-you) ten-you:1R10=[5]=5


ファンブル。

23:03 (ten-you) #1r10

23:03 (ten-you) ten-you:1R10=[1]=1 Famble!!


クリティカル

23:13 (ten-you) #5r7

23:13 (ten-you) ten-you:5R7=[4,9,3,7,7]=10 Critical!!

23:13 (ten-you) ten-you:3R7=[4,2,7]=20 Critical!!

23:13 (ten-you) ten-you:1R7=[10]=30 Critical!!

23:13 (ten-you) ten-you:1R7=[5]=35

当たり前だけど、やっぱりクリティカル値が1下がるとそれだけで全然クリティカル率が変わりますな。


var achieve = new Object();

オブジェクトを作る、という命令。リファレンスで見かけたことはあったのですが、いったいいつ使うものなのかさっぱり見当もついてませんでした。

その一方で、「ファンクションの戻り値が一つしか返せなくて不便だーprefixオブジェクトみたいに複数返せないかなー」と嘆いていた不思議。

海月歩空様ありがとうございます。


pushメソッド

もうひとつ初めて知ったのが「push」メソッド。

概要

与えられた要素を追加することによって配列を変異させ、その配列の新しい長さを返します。

no title

配列にあとから要素を追加するメソッドだったんですね。


追記

function event::onChannelText(prefix, channel, text)
{
    //結果返却用オブジェクト
    var achieve = new Object();
    //結果返却用オブジェクト内結果文字列用配列
    achieve.array = new Array();
    //結果返却用オブジェクト内達成値
    achieve.value = 0;
    
    if (text.match(/#\d+R\d+/ig)){

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/#(\d+)R(\d+)/i);
         
        //サブファンクションに飛ばす
        achieve = renegadeDice(matchedText[1],matchedText[2]);
        
        //結果を出力
        for (i=0; i < achieve.array.length; i++){
            send(channel, prefix.nick + ':' + achieve.array[i] );
        }

    }
}

    //結果返却用オブジェクト
    var achieve = new Object();
    //結果返却用オブジェクト内結果文字列用配列
    achieve.array = new Array();
    //結果返却用オブジェクト内達成値
    achieve.value = 0;

の部分。

オブジェクトの受け取りだから必要かと思って書いてましたが、以下のように省略してもOKでした。

function event::onChannelText(prefix, channel, text)
{
   
    if (text.match(/#\d+R\d+/ig)){

        //該当文字列を配列matchedTextに格納
        var matchedText = text.match(/#(\d+)R(\d+)/i);
         
        //サブファンクションに飛ばす
        var achieve = renegadeDice(matchedText[1],matchedText[2]);
        
        //結果を出力
        for (i=0; i < achieve.array.length; i++){
            send(channel, prefix.nick + ':' + achieve.array[i] );
        }

    }
}

サブファンクションに飛ばす時に初めてvarつけて変数として宣言してます。

これで十分動きました。柔軟だなあ。