Minecraft Modding

コードが書きたい。

はてなブログの記事と記事の間にアドセンス広告を貼りたい

概要

Google Adsenseの審査が通ったので広告を設置していたのですが、Topに表示される記事と記事の間に広告を入れたかったもののデザインページにそれらしいセクションがなく、Javascriptで挿入することにしました。

コード

JQuery非依存で頑張って実装したコード

<script>
/**
 * 記事一覧の記事と記事の間にGoogle Adsense広告を表示する。
 * JQuery非依存です。
 */

// 広告を挿入する
function insertAdsbygoogle() {
    // 広告表示対象のクラス名
    var adsenseTag = "adsbygoogle";
    // 広告表示後の退避クラス名
    var adsenseViewedTag = adsenseTag + "-viewd";

    // getElementsByClassNameの結果を配列に変換
    Array.prototype.forEach.call(
        // フッターの子要素に広告を挿入する
        document.getElementsByClassName("entry-footer"),
        // 配列全ての要素へ挿入する(foreach)
        function(value, index, array) {
            // 広告用のタグを挿入する
            var ins = document.createElement("ins");
            ins.classList.add(adsenseTag);
            
            // TODOそれぞれに適切な文字列を設定してください
            ins.setAttribute("style",              "TODO");
            ins.setAttribute("data-ad-format",     "TODO");
            ins.setAttribute("data-ad-layout-key", "TODO");
            ins.setAttribute("data-ad-client",     "TODO");
            ins.setAttribute("data-ad-slot",       "TODO");
            value.appendChild(ins);
            
            // 広告を表示させる
            (adsbygoogle = window.adsbygoogle || []).push({});
            
            /**
             * 広告用のタグのクラス名を変更する。
             * adsense側のコードが最初に見つけたadsbygoogleの要素にのみ広告を表示する為、
             * 処理済みのタグはクラス名を一時的に変更する。
             */
            ins.classList.add(adsenseViewedTag);
            ins.classList.remove(adsenseTag);
        }
    );
    
    Array.prototype.forEach.call(
        document.getElementsByClassName(adsenseViewedTag),
        function(value, index, array) {
            // 変更していた広告用のクラス名を元に戻す
            value.classList.add(adsenseTag);
            value.classList.remove(adsenseViewedTag);
        }
    );
};

/**
 * ガード節 Topページでなければ実行しない。
 * bodyはページに対して一つしかないはず。
 */
if(document.querySelectorAll("body.page-index").length == 1) {
    // 広告を挿入する
    insertAdsbygoogle();
}
</script>

 残念ながらこちらのコードはまるまるコピペしても動きません。TODO文字列に適切な文字列を設定してご利用ください。ちなみに貼り付け先はフッターです。

 

終わりに

当初はJQueryを利用したコードでしたが、この為だけにいちいち外部ソース読み込むの手間かなと思い非依存を目指して作りましたが、どうやら既にどこかしら使われているらしく別途読み込む処理を書かずに動作するようでした。間違ってたらすみません。

ネイティブ インフィード、レスポンシブの広告を貼る為に作りましたが、他種の広告でも利用できるかは未確認です。また、当ソースコードは自由に利用して貰って大丈夫ですが、責任までは持てないので自己責任での利用をお願いします。

MinecraftForgeでJavassistを使いたい

目次

 

概要

Mod開発時に既存クラスを変更したいなと調べているうちにForge側で方法が用意されていることを知ったが、ASMを利用する方法でちんぷんかんぷんだったのでJavassistを使いたいなとやってみました。

当初公開していたコードが全く!javassist使っていないことに気付いてコードを修正しました。参考にならないコードを載せてしまってすみません。

 

今回のコード

build.gradleの一部抜粋

dependencies {
    //compile group: 'org.javassist', name: 'javassist', version: '3.15.0-GA'
    // 上記のバージョンだとJava8で落ちてしまうようで最新のものにしました
    compile group: 'org.javassist', name: 'javassist', version: '3.22.0-GA'
}
//manifestファイルの書き込みをビルドに追加する。これにより、coremodの読み込みが可能になる。
//なお、テスト起動時にはこのオプションが効かないためVMオプションに以下の引数を追加する。
//-Dfml.coreMods.load=coremodexample.asm.CoreModExamplePlugin
jar {
    manifest {
        //coremodのパスを指定する。
        attributes FMLCorePlugin: 'coremodexample.asm.CoreModExamplePlugin'
        //今回はAluminiummodが別で入っているためそれも読み込む。
        attributes FMLCorePluginContainsFMLMod : 'true'
    }
}

 

CoreModExamplePlugin.java

package coremodexample.asm;

import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;

import java.util.Map;

/**
 * クラス書き換え用のコアクラス。
 * Modアノテーションを付けているクラスとは別に必要なことに注意。
 */
@IFMLLoadingPlugin.TransformerExclusions({"coremodexample.asm"})
public class CoreModExamplePlugin implements IFMLLoadingPlugin {
    //書き換え機能を実装したクラス一覧を渡す関数。書き方はパッケージ名+クラス名。
    @Override
    public String[] getASMTransformerClass() {
        return new String[]{"coremodexample.asm.CoreModExampleTransformer"};
    }

    //あとは今回は使わない為適当に。
    @Override
    public String getSetupClass() {
        return null;
    }

    @Override
    public void injectData(Map<String, Object> data) {
    }

    @Override
    public String getAccessTransformerClass() {
        return null;
    }

    @Override
    public String getModContainerClass() {
        return null;
    }
}

 

CoreModExampleTransformer

package coremodexample.asm;

import javassist.ClassPool;
import net.minecraft.launchwrapper.IClassTransformer;

public class CoreModExampleTransformer implements IClassTransformer {
    @Override
    public byte[] transform(final String name, final String transformedName, byte[] baseClass) {
        // javassist系のクラスを除外しないと循環してしまう
        if(name.matches("javassist.*")) { return baseClass; }

        // 全てのブロックのコアクラスだけ処理する
        if(!name.equals("net.minecraft.block.Block")) { return baseClass; }

        // 読み込むクラス全て?に対して実行される為出力が量産される
        System.out.println("CoreModExample transform.");
        System.out.println("name :" + name);
        System.out.println("transformedName :" + transformedName);

        try {
            return this.patchClass(name, transformedName, baseClass);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return baseClass;
    }

    /*
    修正前
    private byte[] patchClass(String name, String transformedName, byte[] baseClass) {
        ClassPool cp = ClassPool.getDefault();

        return baseClass;
    }
    */
    
    /**
     * クラス書き換える。
     * @param name 絶対パスのクラス名
     * @param transformedName 分からない
     * @param baseClass クラスのバイナリ
     * @return 書き換え後クラスのバイナリ
     */
    private byte[] patchClass(String name, String transformedName, byte[] baseClass) throws NotFoundException, IOException, CannotCompileException {
        ClassPool classPool = ClassPool.getDefault();

        // バイトコードからクラス定義を読み込む
        InputStream inputStream = new ByteArrayInputStream(baseClass);
        CtClass ctClass = classPool.makeClass(inputStream);

        // フィールドの追加
        ctClass.addField(CtField.make("private static int counter = 0;", ctClass));

        // コンストラクタの読み込み
        CtConstructor ctConstructor = ctClass.getDeclaredConstructor(new CtClass[] {
                classPool.get("net.minecraft.block.material.Material"),
                classPool.get("net.minecraft.block.material.MapColor")
        });

        // コンストラクタの実装の文末に処理を追加する
        // 本当はブロック名を表示したかったが、コンストラクタで名称を読んでいないので出来なかった
        ctConstructor.insertAfter("System.out.println(\"This Counter : \" + counter);");
        ctConstructor.insertAfter("counter++;");

        return ctClass.toBytecode();
    }
}

 

課題
  • JarをModsに入れても読み込まれない

 

終わりに

久方ぶりの投稿でご無沙汰していました。Mod開発の中で新しいブロックを追加するよりも既存の機能をちょっと書き換えた方が自然なんじゃないかと思うことがあってやり方を調べていました。Javaのリフレクションの機能からASM、Javassistと調べて今回のようになりました。

最初はASMでやろうと思ったのですが、バイトコードがどうとかさっぱり頭に入らず鬱々しながら英語の公式サイトを眺めていてたまらずJavassistに逃避しました。

あまりにも投稿していなかったので死滅するかと思いましたが投稿出来てほっこりする思いです。はー楽しかった。

IntelliJ IDEAってカッコいいよね for Minecraft Modding

目次

 

概要

 Eclipseでプロジェクトを作るとmcmod.infoがちゃんと読み込まれず、起動時のMod情報に${version}と表示されていて面倒くささを感じてしまい、いっそIntelliJ IDEAに変えてみました。その時の手順を残しておきます。

IntelliJ IDEAのインストールは割愛して、MDKを展開したところから始めます。

下記のバージョンを想定しています。

 

IntelliJ IDEAってなんぞ?

EclipseNetBeansと競合するJava開発用のIDEで今回はCommunity版を使います。

 

プロジェクトを開く

 一応プロジェクトのフォルダの状態を載せておきます。MDK展開直後です。

f:id:atushi-info:20171031231922p:plain

 

IntelliJ IDEAからOpen File or Projectでこのフォルダを選択します。

f:id:atushi-info:20171031232516p:plain

OKで次。

f:id:atushi-info:20171031232402p:plain

恐らくそのままで良いはず。OKでプロジェクトが作られ立ち上がります。

 

Minecraftデコンパイル

プロジェクトを読み込めたらメニューのView -> Tool Windows -> Gradleから画面の右サイドのようなツールバーを表示出来ます。

f:id:atushi-info:20171031232930p:plain

よく見るMinecraft Mod環境構築記事ではgradlew.batからデコンパイル等を実行しますが、このGradleツールバーからそれらのコマンドを実行出来ます。

早速setupDecompWorkspaceを実行します。が、少なくとも自分のPCでは悲しいほど時間がかかるのでYoutubeとかで時間をつぶします。

 

build.gradleをインポート

次に画面中央のハイライトされた部分のOk, apply suggestion!をクリックします。

完了するとProjectツリーのExternal Librariesにいくつか追加されているはずです。

f:id:atushi-info:20171101001553p:plain

 

プロジェクトのセットアップ

 最後にGradleツールバーのTasks -> forgegradle -> genIntellijRunsを実行します。

クライアントを実行する際には同じくGradleツールバーのTasks -> forgegradle -> runClientを実行すると立ち上がります。geneIntellijRunsのすぐ下にありますね。

鉱石系のブロックを一括破壊する

目次

 

概要

適性の採取ツールがピッケルのブロックを一括破壊するModを作ってみました。

このModでは一括破壊するピッケルを追加する方法で実装しています。既に一括破壊Modは存在しますがキーによるON/OFFだと間違えてしまうことが多々あったため持ち替えた方が良いのではと作ってみました。

主要なコード

それでは早速コードです……。
ちょっと、だいぶとんちんかんなコードを書いてしまいまして非常に分かりにくいと思います……。

github.com

新しくピッケルを追加する

ツールは一から定義するのではなく流用した方が間違いがないと思い、ItemPickaxeを継承しました。ItemPickaxeはピッケル系ツールの実装で、コンストラクタ引数に材質を渡すことで一つのクラスでダイアモンド、鉄、石、木製のピッケルとして機能します。今回はダイアモンド製のツールとして実装しています。

MagicPickaxe.java

public class MagicPickaxe extends ItemPickaxe {
    // 省略
}

 

ピッケルに一括破壊する機能を作る

今回はツールでブロックを破壊した時に呼び出されるonBlockDestroyedメソッドを上書きすることで実装しました。その中でもブロックを破壊する処理はdestroyBlocksAtOnceメソッドに書き出しています。

処理のフローイメージ

  1. 破壊したブロックの周辺を探索する
  2. 破壊したブロックと同じブロックは専用の配列に登録する
  3. 1-2を隣接ブロックから同じブロックがなくなるまで続ける
  4. 破壊予定ブロックの配列から一つ取り出す
  5. 配列から取り出したブロックを破壊する
  6. 4-5を配列が空になるまで続ける
破壊したブロックをまとめる

 そうして隣接している同じブロックをすべて破壊し終えたら、ちらばっているエンティティ(モブやドロップアイテムなどの総称)から破壊したブロックと同じブロックを集め、一つのエンティティにまとめます。

この処理もonBlockDestroyedメソッドで実装していますが、作り終わってからはちょっと違和感を覚えています。

課題
  •  ブロック探索の実装が非常に分かり難い(きっと来月には自分も分からなくなるでしょう)
  • タイミング的に破壊したブロックと一括破壊したブロックのエンティティをひとまとめに出来ない
  • そもそも別途ツールを追加するよりも既存のツールに金床などで一括破壊機能を付加する方がツールを定義しなくても済む気がする

と作ってみて感じています。

Github

 

github.com

 

 

食べ物を追加するMod その3 Jarファイルを生成する

目次

 

これから何する

ついに食べ物を追加するModシリーズ最後になりました。最後は実際にゲームに導入出来るように一つのファイルzipだったりjarだったりはありますがまとめる作業をします。

開発環境
ビルドなるものの設定

一つにまとめる際に生成するjarファイル名や、どのクラスを含めるかなどを指定する必要があります。多くはないのでささっとやります。

まずは次の場所を見つけましょう。

build.gradle

// 省略

version = "1.0"
group = "com.yourname.modid" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "modid"

// 省略

 そして次のように編集します。

// 省略

version = "0.0.1"
group = "portionmod" // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = "portionmod"

// 省略

version
mcmod.infoのバージョン情報と合わせましょう。

group
PortinomModクラス(Mod本体)の存在するパッケージ階層を書きましょう。今回はソースフォルダ直下に作っているで質素です。

archivesBaseName
生成されるMod.jarファイルの名称です。あまり悩みたくなかったのでModIDと同名にしました。

ビルド

この記事の本体ともいえるビルドです。この作業でおなじみのModファイルを生成出来ます。
コマンドラインから操作することも出来ますが、今回は分かりやすくビルドを実行するファイルを作りたいと思います。
プロジェクトの直下に次のファイルを作ります。

gradlew_build.bat

.\gradlew.bat build
pause

ファイルを作成したらさっそく実行しましょう。
真っ黒いウインドウに「BUILD SUCCESSFUL」と表示されていれば完了です。

Jarファイルはどこに出来るの?

「portionmod-0.0.1.jar」がそうです。modsフォルダに配置して動作確認してみましょう。

f:id:atushi-info:20170922171828p:plain

 Github

github.com

最後に

これで食べ物を追加するModシリーズを終わります。なかなか慣れない作業で読みにくく分かりにくいこともあったと思いますが、ここまで読んでいいただいてありがとうございました。
MinecraftあるいはそのMod開発により興味を持っていただけたら嬉しく思います。