maple & chicken

適当に垂れ流す。メイプルのエミュ鯖に関する話がメインになるとおもいます。

JS 基本編

はじめに

この記事は8割の憶測と2割の実測で書かれています。英語が読める方は海外のフォーラムにある投稿を読んだほうが良いでしょう。
私は本職の人でも無いのでそういう方から見れば何言ってんだこいつ、と思われるかもしれませんが温かい目で見てください。
間違いを発見した場合コメントで教えてくれると私が喜びます。
あと、多少プログラムの知識がある前提で書いています。
jsの構文等についてはここでは触れませんので適宜他サイトで勉強してください。

とりあえずエミュ鯖で使うjsの基本的な仕様をまとめましたのでご覧ください。

action()は何度でも呼ばれる

function action(mode, type, selection) {
    cm.sendOk("メッセージ");
}

試しにこのjsを動かしてみてください。

動画を用意しました。

動かすとわかると思いますがOKを押しても同じメッセージが表示されます。
これはOKボタンを押したり、選択肢を選んだりといったアクションをするたびにaction()が呼び出されるためです。
左下のEND CHATを押すとaction()は呼ばれないため会話が終了します。(sendSimple()のみ特殊でaction()が呼び出されます)
ただ処理の途中でcm.dispose()が実行された場合に限っては何らかのアクションがあってもaction()は呼び出されません。

処理はすべて同時に実行される

これもサンプルを用意しました。

function action(mode, type, selection) {
    cm.sendOk("メッセージ");
    cm.gainItem(5220000, 1);
}

こちらは実行するとメッセージが出て同時にアイテムが貰えます。
正確には上から順番に実行され、同時では無いのですがsendOk()等でウィンドウを表示させたからと言って動作が一時停止するわけでも無く、普通に最後まで実行されます。

function action(mode, type, selection) {
    cm.sendOk("メッセージ1");
    cm.sendOk("メッセージ2");
}

こちらの場合メッセージ1は表示されますが、メッセージ2はすでにメッセージ1が表示されているため何も起こりません。
言ってしまえばバグの様なものです、action()1回につきsend等のメッセージを表示させる関数は1つだけ実行されるようにしましょう。

start()について

function start() {
    action(1, 0, 0);
}

function action(mode, type, selection) {
    cm.sendOk("メッセージ");
}

上2つのサンプルでは使っていませんがこのようにしてstart関数を書くことで会話がスタートする時action()が呼ばれるのではなくstart()が呼び出されるようになります。
どちらかと言えばこっちが正式な書き方のような気がします。
最初に呼び出されてからはもう呼び出されることはありませんので、会話の最初にする処理(statusの初期化など)が普通書かれます。
ちなみに関数内にcm.dispose()がありstart()1回で処理が終了するものであればaction()を省いでも大丈夫です。

var status = 0;

function start() {
    if(status) {
	cm.sendOk("メッセージ2");
	cm.dispose();
    } else {
	status++;
	cm.sendNext("メッセージ1");
    }
}

動かない例。メッセージ1でNEXTを押すとaction()を呼び出そうとするのでエラーが出ます。

一度の処理で2つ以上のsend系が実行されるのは意味がありませんのでsend系はaction関数内に固めて、start関数内にはsend系は使わないのが無難でしょう。
しかし、表示させたいメッセージが2つ程度であれば

function start() {
    cm.sendNext("メッセージ1");
}

function action(mode, type, selection) {
    cm.sendOk("メッセージ2");
    cm.dispose();
}

このように書いても良いかもしれません。

引数は省略できる

function action() {
    cm.sendOk("メッセージ1");
    cm.dispose();
}

エラーも出ずに動いてくれます。
modeのみ使う場合は

function action(mode) {

このようにしても大丈夫です。
ただしsendSimple()等、3つ目の引数(selection)を使う場合は3つとも書かなくてはいけません。

今回は以上です。まとめ方下手なので分かりづらいと思います、すみません。

追記

function action() {
    cm.dispose();
    cm.gainItem(5220000,1);
    cm.sendOk("メッセージ");
}

cm.dispose()に関してですがこの順番でも最後のsendOk()まで動いちゃいます。
要はdispose()されてようが最後まで実行しちゃうようです。
途中で処理を止めたい場合は

function action() {
    cm.dispose();
    cm.gainItem(5220000,1);
    return;
    cm.sendOk("メッセージ");
}

このようにすると最後のsendOk()は実行されません。

JS send,ask系 メモ①

sendOkやsendYesNoなどの関数はそれぞれでmodeやtypeが変わる。(selectionも)
例 cm.sendYesNo()でNoを押した場合 mode=0, type=2となる。

以下それのまとめメモ 普通に関数の一覧としても使える(Maplecrystal v117使用)

関数 ボタン mode type selection 説明
cm.askMapSelection(string) GO 1 17 選んだやつ 次元の扉用。2083006か9010022でしか使えない。使えた(2083006だと枠が3つになる)
CANCEL 0 17 -1 "#0#マップ名1#2#マップ名2"という様に入れる
cm.sendNext(string) Next 1 0 -1 Nextボタンのみ
cm.sendPrev(string) OK 1 0 -1 OKボタンとPREVボタン
PREV 0 0 -1
cm.sendNextPrev(string) NEXT 1 0 1 NEXTボタンとPREVボタン
PREV 0 0 -1
cm.sendOk(string) OK 1 0 -1 多分一番よく使うであろうOKボタン
cm.sendYesNo(string) YES 1 2 -1 使いたいけど割りと使うのが難しいYesNoボタン
NO 0 2 -1
cm.sendAcceptDecline(string) ACCEPT 1 15 -1 ACCEPTorDECLINE、クエストでよくつかうやつ
DECLINE 0 15 -1
cm.askAcceptDecline(string) 上と同じ
cm.askAvatar(string, int[]) OK 1 9 配列内の位置 整形のやつ、配列で顔のIDを与える
CANCEL 0 9 -1
LEAVE SHOP 0 9 -1
cm.sendSimple(string) 選択肢(NEXT) 1 5 選んだやつ 選択肢。一番オーソドックスな分岐
END CHAT 0 5 -1 なんかこれだけENDCHATで動作が終了しない
cm.sendStyle(string,int[]) OK 1 9 配列内の位置 髪型のやつ、配列で髪型のIDを与える
CANCEL 0 9 -1
LEAVE SHOP 0 9 -1
cm.sendGetNumber(string,int(初期値),int(最小),int(最大)) OK 1 4 受け取った数字 数字を打たせるやつ。数字じゃないと通らない。負の数はムリ
cm.sendGetText(string) OK 1 3 -1 テキストを取得、空でも通る。cm.getText()でStringとして受け取れる
cm.askAndroid(string,int...) 使用法を調べる気にもならない

こんなところでしょうか。
statusやmodeだけではなくtypeも組み合わせることでJSの幅が広がります。
また今度JSの仕様についていろいろ書きます。

v117.2 日本語化

いろいろあった結果、バージョンはv117.2で落ち着きました。
MapleCrystalを使用しています。備忘録として日本語表示できるまでを残しておきます。
多分Lithiumベースのリパックならこれでできると思います。


tools.data.MaplePacketLittleEndianWriter.java

  • 追加
private static final Charset MS932 = Charset.forName("MS932");
  • 変更

変更前

    public final void writeAsciiString(final String s) {
        write(s.getBytes(ASCII));
    }

    public final void writeAsciiString(String s, final int max) {
        if (s.length() > max) {
            s = s.substring(0, max);
        }
        write(s.getBytes(ASCII));
        for (int i = s.length(); i < max; i++) {
            write(0);
        }
    }

    public final void writeMapleAsciiString(final String s) {
        writeShort((short) s.length());
        writeAsciiString(s);
    }

変更後

    public final void writeAsciiString(final String s) {
        write(s.getBytes(MS932));
    }

    public final void writeAsciiString(String s, final int max) {
        if (s.getBytes(MS932).length > max) {
            s = s.substring(0, max);
        }
        write(s.getBytes(MS932));
        for (int i = s.getBytes(MS932).length; i < max; i++) {
            write(0);
        }
    }

    public final void writeMapleAsciiString(final String s) {
        writeShort((short) s.getBytes(MS932).length);
        writeAsciiString(s);
    }


tools.data.LittleEndianAccessor.java

  • 変更

変更前

    public final String readAsciiString(final int n) {
        final char ret[] = new char[n];
        for (int x = 0; x < n; x++) {
            ret[x] = (char) readByte();
        }
        return new String(ret);
    }

変更後

    public final String readAsciiString(final int n) {
//char ret[] = new char[n];
        final byte ret[] = new byte[n];
        for (int x = 0; x < n; x++) {
            ret[x] = (byte) readByte();
        }
        try {
            String str = new String(ret, "MS932");
            return str;
        } catch (Exception e) {
            System.err.println(e);
        }
        return null;
    }


tools.StringUtil.java

  • 追加
import java.nio.charset.Charset;
private static final Charset MS932 = Charset.forName("MS932");
  • 変更(RightとLeftで二箇所)

変更前

for (int x = in.length(); x < length; x++) {

変更後

for (int x = in.getBytes(MS932).length; x < length; x++) {

Linux環境では内部の文字コードがutf8なのでgetBytes()としてしまうとMS932とバイト数に差が出るためエラーが起きる。
getBytes(MS932)とすることで解決。
キャラクター名なども問題なく日本語が使えます。

f:id:gurinhu:20160926163532p:plain
おわり

JCEのインストールとMySQLのインストール

JDKのインストールのときにJCE入れるのを忘れていたのでここで書いておく。

chicken@PiStory:~ $ wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip
chicken@PiStory:~ $ unzip jce_policy-8.zip
Archive:  jce_policy-8.zip
   creating: UnlimitedJCEPolicyJDK8/
  inflating: UnlimitedJCEPolicyJDK8/local_policy.jar
  inflating: UnlimitedJCEPolicyJDK8/README.txt
  inflating: UnlimitedJCEPolicyJDK8/US_export_policy.jar

先程と同じようにDL、解凍。

chicken@PiStory:~ $ cd UnlimitedJCEPolicyJDK8/
chicken@PiStory:~/UnlimitedJCEPolicyJDK8 $ cp US_export_policy.jar local_policy.jar /opt/jdk1.8.0_101/jre/lib/security/

二つのファイルを/opt/jdk1.8.0_101/jre/lib/security/にコピー 他のサイト見てると気が狂ったようにコピーしまくってるが多分この一箇所だけでOK

続いてWZのXMLを置く これも共有フォルダから引っ張ってくる

chicken@PiStory:~ $ cp /mnt/wz.zip wz.zip
chicken@PiStory:~ $ unzip wz.zip -d PiStory

MySQLのインストール

chicken@PiStory:~/PiStory $ sudo apt-get install mysql-server

途中でパスワードの設定がある。

文字コードの設定をする

chicken@PiStory:~/PiStory $ sudo vim /etc/mysql/my.cnf

[client]
default-character-set=utf8
  
[mysqld]
collation-server = utf8_unicode_ci
init-connect='SET NAMES utf8'
character-set-server = utf8
  
[mysqldump]
default-character-set=utf8

各所に書き足す。

chicken@PiStory:~/PiStory $ sudo /etc/init.d/mysql restart
[ ok ] Restarting mysql (via systemctl): mysql.service.

リスタートしてから確認する。

chicken@PiStory:~/PiStory $ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 37
Server version: 5.5.52-0+deb8u1 (Raspbian)

Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> status
--------------
mysql  Ver 14.14 Distrib 5.5.52, for debian-linux-gnu (armv7l) using readline 6.3

Connection id:          37
Current database:
Current user:           root@localhost
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         5.5.52-0+deb8u1 (Raspbian)
Protocol version:       10
Connection:             Localhost via UNIX socket
Server characterset:    utf8
Db     characterset:    utf8
Client characterset:    utf8
Conn.  characterset:    utf8
UNIX socket:            /var/run/mysqld/mysqld.sock
Uptime:                 6 min 16 sec

Threads: 1  Questions: 111  Slow queries: 0  Opens: 48  Flush tables: 1  Open tables: 41  Queries per second avg: 0.295
--------------

これでMySQLの設定は完了。 SQLの読み込みを行う。

mysql>source ~/PiStory/SQL/MoopleDEV.sql
mysql>exit

設定いじった後のjarを入れて起動してみる。

chicken@PiStory:~/PiStory $ cp /mnt/MoopleDEV.jar dist/MoopleDEV.jar
chicken@PiStory:~/PiStory $ sh launch.sh
MoopleDEV v83 starting up.

Loading Skills
Skills loaded in 5.086 seconds
Loading Items
Items loaded in 4.672 seconds
Listening on port 8484


Server is now online.

成功。やったぜ。 フルクラもないし接続テストはまた明日。

リパック解凍~起動用シェル作成まで

とりあえずリパックを解凍する 今回はv83のMoopleDEVを使う。

ノートPCの共有フォルダを/mnt/にマウントしてあるのでそこからDLしておいたリパックを持ってくる

chicken@PiStory:~ $ cp /mnt/MoopleDEV.zip MoopleDEV.zip

解凍する

chicken@PiStory:~ $ mkdir PiStory
chicken@PiStory:~ $ unzip MoopleDEV.zip -d PiStory

とりあえずCreateINIを動かしてみる

chicken@PiStory:~/PiStory $ sh create_server_linux.sh
エラー: メイン・クラスnet.server.CreateINIが見つからなかったかロードできませんでした

動かない。

chicken@PiStory:~/PiStory $ java -classpath .:dist/* net.server.CreateINI
Welcome to MoopleDEV's .ini creator

こうすると動いた。 INIが作れたので起動用SHを実行してみるが

chicken@PiStory:~/PiStory $ sh launch_server.sh
: not founder.sh: 2: launch_server.sh:
: bad variable name: export:

まぁ動かない。ので

chicken@PiStory:~/PiStory $ java -classpath .:dist/* -Dwzpath=wz/ -Xmx128m net.server.Server
MoopleDEV v83 starting up.

こうした。 もちろんWZも入れてないしMySQLも動いてないのでエラーが大量に出る。 ただ準備はできたので次回はMySQL入れる。(予定)