ブログ

  • #伺か YAYAでのゴースト開発メモ:現在時刻から指定した数値分ずらした日時を取得する

    今日の100日後の日付、などを取得したい場合に使用する。
    100時間後の日時、なども、いちいち○日○時間などと変換することなく、DateTimeShift(4,100)と指定するだけで求められる(GETSECCOUNT関数の仕様による)。
    100ヶ月後、なども同様。

    DateTimeShift
    {	//------------------------------------------------------------------------------
    //	現在時刻から、
    //	第1引数で指定されたもの(GETTIME関数の戻り値の序数)を
    //	第2引数で指定された数値分だけシフトする。
    //------------------------------------------------------------------------------
    _TIME_ARRAY	 = GETTIME;
    _TIME_ARRAY[TOINT(_argv[0])] += TOINT(_argv[1]);
    GETTIME(GETSECCOUNT(_TIME_ARRAY));
    }

    使い方は、

    DateTimeShift([ずらしたい対象,ずらしたい数量])

    第1引数で指定する数値は、GETTIME関数の戻り値で、変更したい要素の序数。
    第2引数で指定する数値は、ずらしたい数量(マイナスも指定可。小数は指定しても関数内部でTOINTされて整数に変換されてしまうので無意味)。
    引数は全て省略可能(その場合、現在時刻がそのまま返る)。
    具体的には

    DateTimeShift(2,10000) //1万日後の日時を取得

    のように書く。

  • #伺か YAYAでのゴースト開発メモ:時間差を取得する

    ゴーストが前回終了されてから、どれくらいの間隔を置いて起動されたか、前回のネットワーク更新からどれくらい間が空いているか、などを調べたい場合に使う(前回の終了時刻、ネットワーク更新時刻が何らかの方法で保存されている必要がある)。
    関数内で使用するTimeZoneDiff変数は、OnNotifyInternationalInfoで通知されるものを設定している。
    GETTIME関数については、マニュアル/関数/GETTIME – AYAYA/03を参照。

    GetInterval
    {	//------------------------------------------------------------------------------
    //	現在時刻と、引数で渡された時刻の差を返す。
    //	引  数:GETTIME関数で取得される日時の配列
    //	戻り値:差分(秒数)、差分(日数)、GETTIME関数で取得される日時の配列
    //		※GETTIME関数で取得される日時の配列の番号が2つズレる
    //------------------------------------------------------------------------------
    _Interval = GETSECCOUNT - GETSECCOUNT(_argv);
    if (_Interval < 0) {
    //インターバルがマイナスの場合、とりあえず0にする。
    _Interval = 0;
    }
    //時差を補正しつつ差分を取得
    _TIME_ARRAY = GETTIME(_Interval + TimeZoneDiff);
    //日数を取得。86400=60*60*24(1日の秒数)。
    _DateDiff = TOINT(_Interval / 86400);
    //戻り値配列を構築
    _Value  = IARRAY;
    _Value ,= _Interval;	//[0]
    _Value ,= _DateDiff;	//[1]
    _Value ,= _TIME_ARRAY;	//[2]~
    _Value;
    }
    OnNotifyInternationalInfo
    {	//------------------------------------------------------------------------------
    //	時差情報の取得(SSP限定)
    //------------------------------------------------------------------------------
    //実際に使用するときに扱いやすいよう、時差を秒数に変換
    TimeZoneDiff = reference[0] * 60;
    }

    使用例

    OnBoot
    {
    StartupTime = GETTIME;		//変数、StartupTimeに、ゴーストの起動日時を格納する。秒数ではなく配列の形でなければならないので、GETSECCOUNT関数ではなく、GETTIME関数を使う。
    }
    OnClose
    {
    _TIME_ARRAY = GetInterval(StartupTime);	//StartupTimeをGetInterval関数に渡して時間差を取得する。
    //取得した時間差を使い、ゴーストが起動されていた時間を喋って終了する。
    STRFORM("\0\s[5]今回の起動時間は、$4d年$02d月$02d日 $2d時間$2d分$02d秒でした!\_w[2000]\-\e",TIME_ARRAY[2],TIME_ARRAY[3],TIME_ARRAY[4],TIME_ARRAY[6],TIME_ARRAY[7],TIME_ARRAY[8]);
    }
  • #伺か YAYAでのゴースト開発メモ:存在するかどうか分からない関数を呼び出す

    ISFUNCとEVALの合わせ技が本来だが、いちいちその2つを並べて書くのは面倒だし、特に、呼び出した関数に何か引数を渡したい場合、さらに面倒になる。
    ので、その辺をひとまとめにした、CallFunctionと言うものを作ってみた。

    CallFunction
    {	//------------------------------------------------------------------------------
    //	存在するかどうか分からない関数を呼び出す。
    //	ISFUNC→EVALの流れをひとまとめにしたもの。
    //------------------------------------------------------------------------------
    _Function = _argv[0];
    _Value	  = _argv[1,_argc-1];
    _Function_EVAL = _Function;
    //引数が指定されていたら引数をセットする
    if (ARRAYSIZE(_Value) > 0) { _Function_EVAL = "%(_Function)(%(CHR(0x22))%(_Value)%(CHR(0x22)))"; }
    //指定された関数を呼び出す
    if (ISFUNC(_Function)){ EVAL(_Function_EVAL); }
    }

    使い方は、

    CallFunction(呼び出したい関数名[,その関数に渡す引数])

    その関数に渡す引数、は、引数が必要ない場合は省略可能。

    CallFunctionの使用例

    OnAnchorSelect
    {	//------------------------------------------------------------------------------
    //	アンカーがクリックされたときの処理(URLジャンプなど)
    //	AYAYA03の、Tips→アンカータグからURLジャンプ、より。
    //	https://emily.shillest.net/ayaya/?cmd=read&page=Tips%2F%E3%82%A2%E3%83%B3%E3%82%AB%E3%83%BC%E3%82%BF%E3%82%B0%E3%81%8B%E3%82%89URL%E3%82%B8%E3%83%A3%E3%83%B3%E3%83%97&word=OnAnchorSelect
    //------------------------------------------------------------------------------
    _id = reference[0];
    // アンカーのIDの冒頭に「http://~」があればWebサイトを開く。
    if (RE_MATCH(_id, '(http|https|ftp)://(.+)')) {
    _url = EscapeText(_id);
    "\C\j[%(_url)] \e";
    // それ以外はIDと同じ名前のイベントへジャンプ
    } else {
    CallFunction('_id')
    }
    }
    MouseCounter
    {	//------------------------------------------------------------------------------
    //	マウス操作の捕捉。引数に'Wheel'を指定するとホイール反応
    //------------------------------------------------------------------------------
    _PreviousArea	= PreviousArea_Move;	//直前のエリア
    _PreviousTime	= PreviousTime_Move;	//直前の時刻
    _Stroke		= Stroke_Move;		//ストローク数
    _Threshold	= 64;			//反応しきい値
    _TYPE  = _argv[0];	//ホイールかそれ以外か
    _Scope = reference[3];	//マウスイベントが発生しているスコープ
    _Area  = reference[4];	//マウスイベントが発生しているエリア
    if (_TYPE == 'Wheel') {
    //ホイール操作に対する反応
    _PreviousArea	= PreviousArea_Wheel;	//直前のエリア
    _PreviousTime	= PreviousTime_Wheel;	//直前の時刻
    _Stroke		= Stroke_Wheel;		//ストローク数
    _Threshold	= 2;			//反応しきい値
    }
    if ((_Area != '')||(_Scope == 1)) {
    //どこかが撫でられている
    if (_Area == _PreviousArea) {
    _Interval = systemuptime - _PreviousTime;
    if (_Interval > 1) {
    //1秒以上間隔が空いたらカウンタをリセット
    _Stroke = 0;マウス反応中 = 0;
    }
    //現在時刻を取得
    _PreviousTime = systemuptime;
    _Stroke++;
    //触られた量が閾値を超えたら「触られている」と判断
    if (_Stroke > _Threshold) {
    //触られた
    if (!マウス反応中) {
    //既に反応中でなければ、触られた部位を見てトークする
    マウス反応中 = 1;
    '\t' + CallFunction('MouseReaction',_TYPE);
    以下省略
  • #伺か YAYAでのゴースト開発メモ:ランダムトークを重複させない

    ゴーストのランダムトークが重複しないようにするには、一般的には以下のようにする。

    RandomTalk : nonoverlap
    {
    'ほげほげ';
    'ごにょごにょ';
    }

    これで大抵はことが足りるが、より徹底して重複を排除するために、次のようにする。

    RandomTalkArray : array    //arrayを指定することで、RandomTalkArray関数の出力(ランダムトーク)が配列として扱われる
    {
    'ほげほげ';
    'ごにょごにょ';
    }
    RandomTalkMain
    {	//------------------------------------------------------------------------------
    //	ランダムトーク。汎用リストを利用し、トークが重複しないようにする
    //	この関数を、OnAiTalkなどから呼び出す。
    //------------------------------------------------------------------------------
    _RandomTalk_Length = STRLEN(JOIN(RandomTalkArray,''));
    if (!ISVAR('RANDOM_TALK_LENGTH')) { RANDOM_TALK_LENGTH = _RandomTalk_Length; }
    if (RANDOM_TALK_LENGTH != _RandomTalk_Length) {
    //トークが変更されたらリストを再作成
    //総件数ではなく文字列長を見ることで、トーク件数が変わらず、内容だけ変更した場合にも対応。
    //文字列長を見るため、トークリストでは変数等の置き換えが発生しないようにすること(""やANYを使わない)。
    Talk_Array = RandomTalkArray;
    RANDOM_TALK_LENGTH = _RandomTalk_Length;
    } elseif (ARRAYSIZE(Talk_Array) == 0) {
    //リストが空っぽ(未定義含む)の場合、リスト再作成
    Talk_Array = RandomTalkArray;
    }
    ANY(Talk_Array);	//ランダムトークを格納した配列から要素を1つ取り出して発話
    Talk_Array[LSO] = IARRAY;	//取り出した要素を配列から削除して、配列を作成し直すまで重複が発生しないようにする
    }

    ランダムトークをトーク配列に格納し、ランダムトークイベントが発生するたびに、ANY関数で1つ選んで発話→LSO関数を使い、選ばれた要素をトーク配列から削除、と言う処理をしている。
    要素の削除を繰り返すと最終的にトーク配列が空っぽになるが、その時は改めてトーク配列にランダムトークを格納し直す。
    要素を配列から削除しているのだから、重複が起こりようがない、と言う理屈である。

    TalkArray変数は、そのままセーブファイルに書き込ませても問題ないと思うが、自分の場合、セーブファイルの内容は必要最低限としたいので、ランダムトークをゴースト終了時に専用のファイルに書き出し、ゴースト起動時にそのファイルから読み込むようにしている。

    その際の処理がこちら。OnSystemLoadで読み出し、OnSystemUnloadで書き出しを行う。
    なお、ファイルへの入出力はUTF-8を指定しているが、必要に応じて別の文字コードに変更することもできる(言うまでもないが、読み書きでは、同じ文字コードを指定すること)。

    OnSystemLoad
    {	//------------------------------------------------------------------------------
    //	ゴーストが読み込まれたときの処理(shiori3.dicからの呼び出し)
    //------------------------------------------------------------------------------
    //ランダムトーク用配列の初期化
    Talk_Array = IARRAY;
    //ランダムトークをファイルから読み込み
    Filename  = 'randomtalk.sav';	//ファイル書き出しでも使うのでグローバル変数に
    FCHARSET(1);	//UTF-8
    if (FOPEN(Filename,'r')) {
    while 1 {
    _talk = FREAD(Filename);
    if (_talk == -1) {
    break;
    }
    Talk_Array ,= _talk;
    }
    FCLOSE(Filename);
    }
    }
    OnSystemUnload
    {	//------------------------------------------------------------------------------
    //	ゴースト終了時の処理(shiori3.dicからの呼び出し)
    //------------------------------------------------------------------------------
    //ランダムトークをファイルに書き出し
    FCHARSET(1);	//UTF-8
    if (ARRAYSIZE(Talk_Array)) {
    if (FOPEN(Filename,'w')) {
    foreach Talk_Array ; _string {
    if (_string != '') {
    FWRITE(Filename,_string);
    }
    }
    FCLOSE(Filename);
    }
    }
    //保存不要な変数を削除し、セーブファイルの肥大化を防ぐ
    ERASEVAR('Filename');
    ERASEVAR('Talk_Array');
    }

    こうすることで、ゴーストの終了を挟んでも、ランダムトークが重複することがなくなる。

  • ゴースト更新情報:よしの・フォルダーソーター

    よしのとフォルダーソーターを更新しました。
    それぞれの主な更新内容は以下の通りです。

    • よしの
      • 祝日関連の処理を変更しました。
        祝日にゴーストを立ち上げたときや、ゴーストの起動中に祝日になったときのトークで、「○○の日です」と言うだけでなく、「○○の日はこういう意味です」と、解説もトークするようにしました。
        なお、解説の内容は内閣府の「国民の祝日」についてから引用しています。
      • 辞書ファイルの内容を整理しました。
        ゴーストの動作としてはそこまで大きな変化はありませんが、ほとんどすべてのファイルに対して内容の整理を行いました。
        そのため、更新ファイル数が多くなるので、アーカイブも更新しました。
    • フォルダーソーター
      • ファイル名被りがあった場合の処理を改善しました。
        分類先でファイル名被りが起きた場合、これまでは元の場所にファイルが残っていましたが、今後、ファイル名被りが発生した場合は、「同一ファイル名あり」と言うフォルダ以下に分類し、元の場所にファイルが残らないようになりました。
        なお、ファイル名被りが複数発生した場合、「同一ファイル名あり_1」「同一ファイル名あり_2」…と言うように、フォルダが増えていきます。
      • 処理中断用のボタンを追加しました。
        処理中に処理を中断するとき、これまでも処理中のバルーンをダブルクリックしてバルーンブレークすることで処理を止めることはできましたが、処理中断用のボタンを追加し、途中で処理を中断できることを分かりやすくするとともに、中断時に適切な後処理をするようにしました。
        なお、処理の関係で、ボタンを何度か連打しないと処理が中断しないことがあります。
      • 前回使用したフォルダを規定値としてセットするようにしました。
        「フォルダを指定する」ダイアログで、前回使用したフォルダをデフォルト値としてセットするようにしました。
        同じフォルダに対して処理を行う場合、いちいち探す必要がなくなります。
      • フォルダ分類処理を改善しました
        フォルダを分類する処理を、より効率的で確実性の高いものにしました。
        これによって、処理がかなり高速化しています(1万件の分類でも3分程度、秒間60件程度)。
      • 辞書ファイル構成を変更しました。
        辞書ファイルの構成を、最新版のYAYAテンプレートゴーストのものに合わせて変更しました。そのため、アーカイブ更新も行っています。
  • よしの公開20周年!

    2024年9月6日を以て、よしのが公開から20周年を迎えます。
    20年の間に見た目や設定も大きく変わりましたが、一番思い入れのあるゴーストである、と言う点は変わっていません。

    その割に、これまでのアニバーサリーでは何もしてやれなかったので、さすがに20周年という節目もそれではまずいだろう、と言うことで、今回は少し記念日らしいことをしてみました。

    • ゴーストに20周年記念トークを追加しました。
      9月6日の最初の起動時にトークするほか、9月6日から13日までの間、通常のランダムトーク中に低確率で20周年記念トークを喋ります。
    • pixivとカクヨムで、よしのを主人公とした小説を公開しました。
      連載中、と言う形で、続きは随時公開する予定となっています。

    細く長く、とは言え、20年間も継続して何かをする、と言うのは、ゴースト以外ではちょっと思いつきません。それだけ、ゴーストというものが自分にとってしっくりくるものであり、マイペースで楽しめるものだった、と言うことなのでしょう。

    よしのの推奨ベースウェアであるSSPやSHIORIエンジンのYAYAの開発・保守をしてくださっているぽなさんを始め、ネット上やリアルにおいて、様々な形でこれまで支えてくださった皆様に感謝いたします。

    公開から20周年を迎えたよしのですが、今後もゴースト・小説ともに更新をしていく予定ですので、今後ともよろしくお願いいたします。

    ※マヤも今年の1月に20周年を迎えていましたが、近いうちによしのと統合する予定があるため、アニバーサリートークの実装などは見送りました。

  • ゴースト「よしの」更新しました

    かなり久しぶりではありますが、ゴースト「よしの」を更新しました。
    変更点が多いため、アーカイブも更新しています。

    主な変更点は以下の通りです。
    1.SHIORIを最新版へアップデート
    2.「眼鏡なし」モードを削除

    1.はともかく、2.について少し補足。

    私のTwitterをご覧になっている方はご存じの通り、私はかなり重度のメガネスキーで、メガネキャラ(基本メガネっ娘だが、カッコ良ければ男でもバッチコイ)をこよなく愛するものの一人である。

    そして、メガネスキーあるある、だが、「話の途中で、眼鏡キャラが特に理由もなく眼鏡を外す」「”お前、眼鏡がない方が可愛いな”と言うような台詞(”眼鏡がなくても”ならギリセーフ)」と言うものに対して、私も少なからず嫌悪感を抱く側である。

    そんな私が、自分のオリジナルキャラクターに、特に理由もなく眼鏡を外しているモードを搭載しているというのは、はっきり言って自己矛盾なのではないか?と言うことをしばらく前から考えていた。

    この自己矛盾を解決するには「メガネスキーであることを止める」「眼鏡なしモードを無くす」のどちらかしかない。
    そして、前者が実現不可能である以上、選択肢は後者しかあり得ない。

    という訳で、「眼鏡なしモード」は、ゴースト「よしの」からは「永久に」削除されたのだが、これは単に「メガネスキーとしての自己矛盾を解決するため」と言うことだけが理由ではない。

    Twitterでは何度か言っているが、公開中のゴーストのうち、よしのと関係が深いゴーストを統合する計画を立てている。
    その際、当然ながらシェルも統合する必要があるが、よしののサーフェス定義の多さがネックとなる可能性があった(トークも統合する必要があるが、単純に追加していくだけでも何とかなるため、シェル統合よりも負担は少ない…と予測している)。
    そのため、自身のポリシー的にも問題があった「眼鏡なしモード」を削除し、サーフェス定義を整理し直すことで、将来の統合に向けた下準備を行ったのである。

    よしのを中心に各種ゴーストを統合することで、トークの幅を広げ、それぞれのキャラクターがもっと「生きた」状態になれば、と考えている。
    今回の更新はそのための第一歩である。

    追記:一部ファイル名等に不具合があったため、公開中ゴーストの一斉ネットワーク更新等を行いましたが、内容に変化はありません。
    追記2:ゴースト「時報」は開発・更新を終了しました。

  • SSL対応しました

    昨今の情勢に合わせ、hironet.jpをSSL対応にしました。
    最新版のOS・ブラウザを使用している場合は特に影響はないと思いますが、一部ベースウェア等でゴーストのアップデートをする際に失敗する可能性があります。

  • magi-system.org廃止します

    長らく保持しつつけてきたmagi-system.orgのドメイン名ですが、表だって使わなくなって久しいので、ドメイン管理会社に廃止の手続きをしました。
    廃止予定日は2016年09月10日となります。

    ドメインの廃止に伴い、以下のような影響が見込まれます。

    • ドメイン廃止予定日以降は、当サイトに対するmagi-sytem.orgでのアクセスができなくなります。
    • ドメイン廃止予定日以降、他者によってmagi-system.orgが取得され、新たなサイトが開設される可能性がありますが、それらのサイトは当方とは一切関係ありません。それらのサイトについて当方にお問い合わせになっても一切お答えすることはできませんのでご了承下さい。
    • 現在でも当サイトへのリンクとしてmagi-system.orgを使用されている方は、ドメイン廃止予定日までに、新ドメインであるhironet.jpへの更新をお願いいたします。

    皆様には大変お手数をおかけして申し訳ありませんが、よろしくお願い致します。

    ※補足。今回廃止するのは、magi-system.orgのドメイン名のみです。hironet.jpのドメイン名や、現在公開中のサイトなどはそのまま存続します。