SpongeプラグインでSpringのDIコンテナを使う

MinecraftのSpongeプラグインでSpringを使ってみたのでその時のメモです。

Springを使うメリット
・テストがやりやすくなる
・AOPが使える←個人的にかなり重要

なぜAOPを使いたいか
継承では解決できない同じような処理がサーバーのプラグインでは結構出てくる(ロギングとか権限周り)
その辺をAOPで解決したい

プラグインのエントリポイント

package net.orekyuu.spring;

import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameConstructionEvent;
import org.spongepowered.api.plugin.Plugin;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

@Plugin(
        id = "net.orekyuu.spring",
        name = "SpringDemo",
        version = "1.0-SNAPSHOT"
)
public class SpringDemo {

    private ApplicationContext context;

    private static SpringDemo INSTANCE;

    @Listener
    public void onServerStart(GameConstructionEvent event) {
        INSTANCE = this;
        //net.orekyuu.spring以下をComponentScanしてる
        context = new AnnotationConfigApplicationContext("net.orekyuu.spring");
    }

    public static SpringDemo getInstance() {
        return INSTANCE;
    }

}

ゲーム起動時にApplicationContextを作るだけです。
プラグイン以下パッケージをComponentScanするようにしておくと、アノテーションベースの設定ができるようになります。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.orekyuu</groupId>
    <artifactId>spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Spring</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <profiles>
        <profile>
            <id>release</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>2.4</version>
                        <executions>
                            <execution>
                                <id>attach-sources</id>
                                <goals>
                                    <goal>jar-no-fork</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>2.10.3</version>
                        <executions>
                            <execution>
                                <id>attach-javadocs</id>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.6</version>
                        <executions>
                            <execution>
                                <id>sign-artifacts</id>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
                <resources>
                    <resource>
                        <directory>src/main/resources</directory>
                        <filtering>true</filtering>
                    </resource>
                </resources>
            </build>
        </profile>
    </profiles>

    <build>
        <resources>
            <resource>
                <directory>\${project.basedir}/src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>templating-maven-plugin</artifactId>
                <version>1.0-alpha-3</version>
                <executions>
                    <execution>
                        <id>filter-src</id>
                        <goals>
                            <goal>filter-sources</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-site-plugin</artifactId>
                <version>3.3</version>
                <dependencies>

                    <dependency>
                        <groupId>net.trajano.wagon</groupId>
                        <artifactId>wagon-git</artifactId>
                        <version>2.0.3</version>
                    </dependency>
                    <dependency>
                        <groupId>org.apache.maven.doxia</groupId>
                        <artifactId>doxia-module-markdown</artifactId>
                        <version>1.6</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <artifactId>maven-release-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <autoVersionSubmodules>true</autoVersionSubmodules>
                    <tagNameFormat>@{project.version}</tagNameFormat>
                    <scmCommentPrefix xml:space="preserve">[RELEASE] </scmCommentPrefix>
                    <goals>install deploy site-deploy
                    </goals> <!-- install is here to fix javadoc generation in multi-module projects -->
                    <releaseProfiles>release</releaseProfiles>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <excludes>
                                    <exclude>junit:junit</exclude>
                                </excludes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
                <configuration>
                    <relocations>
                        <relocation>
                            <pattern>org.apache</pattern>
                            <!--org.apacheパッケージのクラスをforgeが読み込んでくれないっぽいので回避-->
                            <shadedPattern>org.aapache</shadedPattern>
                        </relocation>
                    </relocations>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spongepowered-repo</id>
            <url>http://repo.spongepowered.org/maven/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.spongepowered</groupId>
            <artifactId>spongeapi</artifactId>
            <version>4.1.0-SNAPSHOT</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.2.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>

        <!--AOP-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>RELEASE</version>
        </dependency>

    </dependencies>

    <reporting>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.10.3</version>
            </plugin>
        </plugins>
    </reporting>
</project>

これがpom.xmlです。
注意としてはそのままflat-jarにしてしまうとClassNotFoundExceptionが発生してしまいます。
原因はMinecraftのLaunchClassLoaderにあります。このClassLoaderでModが読み込まれるのですが、org.apache.logging以下パッケージが無視されています。正確にはLaunchClassLoader.class.getClassLoader()でロードされるのですが、LaunchClassLoaderとは親子関係がなくて見つからないみたいな流れ?

そのため解決策としてmaven-shade-pluginを使ってorg.apacheパッケージをリネームすることで回避しました。

イベントをSpringで
Spongeで発生するイベントをSpringのイベントに置き換えてみます。

@Component
public class EventConverter {

    @Autowired
    private ApplicationEventPublisher publisher;

    @Listener
    public void onSpongeEvent(Event event) {
        //SpongeのイベントをSpringに流す
        publisher.publishEvent(new SpongeEvent<>(SpringDemo.getInstance(), event));
    }
}

このComponentはSpongeのイベントをすべて受け取り、Springのイベントへ変換してイベントを通知します。

public class SpongeEvent<T extends Event> extends ApplicationEvent {

    private final T event;

    public SpongeEvent(Object source, T event) {
        super(source); //とりあえず適当に渡しておく
        Objects.requireNonNull(event);
        this.event = event;
    }

    public T getEvent() {
        return event;
    }
}

Spongeのイベントを持つだけのラッパークラスを作りました。ここは特に解説は必要ないと思います。

次に受け取り側です。

//プレイヤーがサーバーに入った時のイベントを受け取ってなにかする
@Component
public class PlayerJoinEventListener {

    @Autowired
    public PlayerMessageService playerMessageService;

    @EventListener
    public void handleOnPlayerJoin(SpongeEvent<ClientConnectionEvent.Join> event) {
        playerMessageService.sendHelloMessage(event.getEvent().getTargetEntity());
    }
}

Playerが入ってきた時のイベントを取ってみました。
受け取りたいイベントを引数にとって、EventListenerアノテーションをつけるだけです。

Spongeのイベント以外にも独自のイベントを作りたい場合はApplicationEventを継承したクラスを作り、ApplicationEventPublisherへ流すだけです。
詳しく知りたい方はこちらを見るのが良いかと思います。
https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

最後に設定です。

@Configuration
public class EventConfig {

    @Autowired
    private EventConverter eventConverter;

    //EventConverterをEventManagerに登録する
    @Bean
    public EventManager eventManager() {
        EventManager manager = Sponge.getEventManager();
        manager.registerListeners(SpringDemo.getInstance(), eventConverter);
        return manager;
    }

}

SpongeのEventManagerにEventConverterを登録しているだけです。

SpringAOPでロギング
AOPの定番ネタですがロギングです。

まずは設定から

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
}

はい。EnableAspectJAutoProxyしてるだけです。

次にロギングのインターセプタを作ります。

@Component
@Aspect
public class LogInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);
    @AfterReturning("execution(public * net.orekyuu.spring.service..*.*(..))")
    public void printLog(JoinPoint joinPoint) {
        logger.info("ServiceLog: " + joinPoint.getSignature());
    }
}

net.orekyuu.spring.serviceパッケージ以下にある全てのpublicなメソッドのどれかが正常終了した時にログを出力しています。
ポイントカット式とかの書き方は以下を見ればよいかと。
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

やりたい設計とか
Controller→Service→Repositoryの構造になんか近いような・・・?
EventListener→Service→Repositoryって結構よさ気では!?

テストとかDBに関してはまた後日

Minecraft1.8サーバープラグイン開発 データベース編

イベント編の続きです。
今回はデータベースを使ってプレイヤーのログインのログをとってみます。
データベースはMySQLを使用しています。

データベースといえばJPAを使用したかったんですが、試行錯誤してもダメでした。
理由としてはMETA-INF/services/の中にあるファイルを読み込めないのが原因のようでした。
というわけでBukkitで用意されているEbeanを使用していきます。

まずMySQLを使用するために依存関係にcompile ‘mysql:mysql-connector-java:5.1.34’を追加します。
次にplugin.ymlにdatabase: trueを追加。これを行うことでデータベースを使用できるようになります。

Ebeanで使用するためのエンティティを作成します。
JPAと同じでjavax.persistence.Entityアノテーションをクラスにつける感じで。
また、デフォルトコンストラクタとゲッタ/セッタが必要です。

@Entity
@Table(name = "PLAYER_JOIN_LOG")
public class PlayerJoinLog {
    @Id
    @GeneratedValue
    private Long id;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(nullable = false)
    private Date joinDate;

    @Column(nullable = false)
    private String playerName;

    @Column(nullable = false)
    private String playerUUID;

    @Column(nullable = false)
    private String joinIP;

    //コンストラクタとゲッタセッタは省略
}

PluginクラスでgetDatabaseClassesメソッドをオーバーライドしてデータベースで使用するエンティティのClassクラスのリストを返します。

@Override
public List<Class<?>> getDatabaseClasses() {
    return Arrays.asList(PlayerJoinLog.class);
}

onEnableされた時にテーブルの存在を確認し、テーブルを作成します。

private static EbeanServer database;
@Override
public void onEnable() {
    setupDatabase();
}

private void setupDatabase() {
    try {
        getDatabase().beginTransaction();
        getDatabase().find(PlayerJoinLog.class).findRowCount();
    } catch (PersistenceException ex) {
        installDDL();
    } finally {
        getDatabase().commitTransaction();
    }
}

public static EbeanServer getPluginDataBase() {
    return getPlugin().getDatabase();
}
public static Plugin getPlugin() {
    return getPlugin(ServerPlugin.class);
}

ListenerでPlayerがログインした時DBに情報を保存しましょう。

@EventHandler
public void onLogin(PlayerJoinEvent event) {
    Player player = event.getPlayer();
    UUID uuid = player.getUniqueId();
    PlayerJoinLog log = new PlayerJoinLog(player.getPlayerListName(), uuid.toString(),
            player.getAddress().getHostName());

    EbeanServer dataBase = MyPlugin.getPluginDatabase();
    try {
        dataBase.beginTransaction();
        dataBase.save(log);
    } finally {
        dataBase.commitTransaction();
    }
}

後はビルドして動作を確認して終了です。
このプラグインを使うためにはBukkit側で設定が必要になります。

実際動かすjarと同じディレクトリに生成されているbukkit.ymlに以下を記述します。

database:
  username: DBのユーザー名
  isolation: SERIALIZABLE
  driver: com.mysql.jdbc.Driver
  password: DBのパスワード
  url: DBのURL

これで設定は以上です。


おまけ

IntelliJでDBの設定(Ultimateの機能だけど)

追加からMySQLを選択
DatabaseTool1

ホストとポート、データベース名とユーザー名パスワードを入力
TestConnectionで接続確認をとれたらOK
DatabaseTool2

データはこのような形で見れるようになります。
データをダブルクリックで編集できます。
DatabaseTool3

SQL文はデータベースかテーブルを右クリックしてConsoleを選ぶと入力用のタブが開くのでそこで入力して実行。
補完めっちゃきいて良い感じです。
実行結果はこんなかんじで表示。
いいですね。IntelliJ
DatabaseTool4

Minecraft1.8サーバープラグイン開発 イベント編

HelloWorld編の続きです。
今回はイベントを使用してログインしてきたプレイヤーにかぼちゃをかぶせるプラグインを作成してみましょう。

Bukkitではサーバーで発生したいろいろなイベントを受け取る仕組みが用意されています。
これを使用することで一定範囲のブロックを破壊できないようにしたり、ログインしてきたプレイヤーにアイテムをプレゼントしたりなどの処理を行えます。

イベントを受け取るリスナはorg.bukkit.event.Listenerインターフェイスを実装します。
Listenerインターフェイスはマーカーインターフェイスになっていて実装しなければいけないメソッドはありません。
イベントを受け取るメソッドはorg.bukkit.event.Eventを継承しているクラス1つを引数に取り、@EventHandlerアノテーションがなければいけません。
引数のクラスによってメソッドは適切なタイミングで呼び出されるようになります。

プレイヤーがログインした時にかぼちゃをかぶせる処理を書いてみます。

public class MyListener implements Listener {

    @EventHandler
    public void onPlayerJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        player.getInventory().setHelmet(new ItemStack(Material.PUMPKIN));
    }
}

非常に簡単ですね。
最後にこのMyListenerをBukkitに登録する必要があります。
registerEventsには第一引数にListener、第二引数にPluginを与えます。

public class HelloWorld extends JavaPlugin {
    @Override
    public void onEnable() {
        getServer().getPluginManager().registerEvents(new MyListener(), this);
    }
}

イベントは独自で定義することも可能です。
複雑なプラグインを作ったりする場合は使うことになると思います。
独自のイベントを作成する場合はEventを継承し、staticなHandlerListを宣言します。
親に定義されているgetHandlersメソッドをオーバーライドしてHandlerListを返すようにします。
追加でHadlerListを返すpublic static HandlerList getHandlerList()を作成します。
これを用意しないとIllegalPluginAccessExceptionが発生します。

イベントを通知する場合はBukkit.getServer().getPluginManager().callEvent(customEvent);のようにcallEventメソッドを使用します。
リスナは@EventHandlerアノテーションのつけられたメソッドで引数にCustomEventを定義するだけです。

これでイベント編終了です。

Minecraft1.8サーバープラグイン開発 HelloWorld編

前回の続きです。
とりあえずHelloWorldでもやってみる。

まずはsrc/main/resources/にplugin.ymlを作成
これはサーバーがプラグインを読み込んだ時に必要な情報をこのファイルからロードします。
必須の項目はname, version, mainの3つです。
nameにはプラグインの名前を文字列で記述します。
versionにはプラグインのバージョンを文字列で記述します。
mainにはJavaPluginクラスを継承したエントリポイントとなるクラスを完全修飾名で記述します。

その他の設定についてはWikiを参考にすると良いと思います。

エントリポイントとなるクラスはJavaPluginクラスを継承します。
プラグインが有効化された時onEnableメソッドが呼び出されるので、プラグインの初期化はonEnableで行います。
コマンドなどでプラグインが無効化された時はonDisableメソッドが呼び出されるので、イベントリスナやタイマー処理はここで終了するようにしておきましょう。

今回は有効化された時にHelloWorldするだけにしておきましょう。

public class HelloWorld extends JavaPlugin {
    @Override
    public void onEnable() {
        System.out.println("HelloWorld!");
    }
}

Minecraft1.8サーバープラグイン開発 開発環境構築編

Bukkitの開発が止まってしまって1.8のプラグイン開発できないなーと思ってたんですけど、Spigotが1.8対応しているみたいだったのでそれで開発してみようと思います。

まずは本体やらを準備しないといけないんですが、自分でビルドを行う必要があります。
公式サイトのDownloadsからBuildTools.jarをダウンロードします。
適当なディレクトリに投げてjava -jar BuildTools.jarのようにjarを実行すればビルドしてくれます。

必要なものが用意できたら、build.gradleを用意します。

ビルドパスにbukkitとspigot-apiを入れたいのでcraftbukkit-1.8.jarとspigot-api-1.8-略.jarをlibsフォルダに入れます。
基本的にspigot-apiのクラスを使用するのですが、Databaseの機能を使いたいのでbukkitも一緒に入れています。必要なければ入れる必要はないと思います。
jarタスクは誰か添削頼みます。groovy力が足りない・・・!じゃけんあとで勉強しましょうね~

開発環境は整いました。おしまい。
気分が乗ったら続き書くと思う。

MinecraftのModdingで必要なjsonを生成するライブラリを作った

Minecraft1.8からItemModelやBlockModelのjsonを書く必要が出てきて面倒じゃないですか?
僕は見た目の部分をコードから排除するのはすごい良いと思うんだけど、使い回しができずにファイルを複製してひたすら増えていくのはどうかと思うんです。

そこでテンプレートをベースにコンパイル時に生成するライブラリを作りました。
コードはこんなかんじになります。

public class BlockSample {

    @BlockModel(name = "block1", args = {"blocks/brick", "blocks/clay"}, template = "sample:block_base")
    @BlockState(name = "block1", args = {"sample:block1"}, template = "sample:state_base")
    @ItemModel(name = "block1", args = {"sample:block/block1"}, template = "sample:item_block_base")
    @Languages({
            @Language(value = "サンプルブロック1", name = "sampleBlock1", lang = LangType.JA_JP, target = LangTarget.BLOCK),
            @Language(value = "Sample Block 1", name = "sampleBlock1", lang = LangType.EN_US, target = LangTarget.BLOCK)
    })
    public static final Block block1 = new SimpleBlock();
    @BlockModel(name = "block2", args = {"blocks/clay", "blocks/brick"}, template = "sample:block_base")
    @BlockState(name = "block2", args = {"sample:block2"}, template = "sample:state_base")
    @ItemModel(name = "block2", args = {"sample:block/block2"}, template = "sample:item_block_base")
    @Languages({
            @Language(value = "サンプルブロック2", name = "sampleBlock2", lang = LangType.JA_JP, target = LangTarget.BLOCK),
            @Language(value = "Sample Block 2", name = "sampleBlock2", lang = LangType.EN_US, target = LangTarget.BLOCK)
    })
    public static final Block block2 = new SimpleBlock();

    public static void init() {
        CoreProxy.proxy.registerBlock(block1, "block1");
        CoreProxy.proxy.registerBlock(block2, "block2");
    }
}

@BlockModelテンプレートはこんな感じ

{
  "parent": "block/cube",
  "textures": {
    "particle": "{1}",
    "down": "{0}",
    "up": "{0}",
    "north": "{1}",
    "south": "{1}",
    "west": "{1}",
    "east": "{1}"
  }
}

テンプレートの{index}は@BlockModelのargsの各インデックスにバインドされています。
これで同じテンプレートからテクスチャだけ違うBlockModelのjsonが生成されます。
同じように@ItemModelと@BlockStateも利用できます。

次に@Languageはlangファイルへの書き込みを行います。
1つのアイテムやブロックに複数の翻訳を指定したい場合は@Languagesに配列で@Languageアノテーションを入れます。
valueに翻訳後の値
nameにunlocalizedName
langに翻訳言語
targetに翻訳のタイプを指定します。
コンパイル時にアノテーションが処理されてlangが生成されます。

次に必須となるアノテーションを紹介

@Mod(modid = SampleMod.MODID, version = SampleMod.VERSION)
@ResourcesDomain("sample")
@ModID(SampleMod.MODID)
public class SampleMod
{
    public static final String MODID = "sample";
    public static final String VERSION = "1.0";
    
    @EventHandler
    public void init(FMLInitializationEvent event)
    {
        ItemSample.init();
        BlockSample.init();
    }
}

これはエントリポイントのクラスですが、クラスに付けられている@ResourcesDomeinと@ModIDがこのライブラリのアノテーションです。
@ResourcesDomeinは生成されるファイルのドメインを指定します。
多重定義はできません。これが使用されると各リソース生成用アノテーション(@BlockModelとか@ItemModel)のdomeinのデフォルト値として利用されます。
複数のドメインを使いたい場合は1つだけ宣言しておき、その他のドメインはdomein要素を指定してください。

@ModIDはModIDを指定します。
これは多重定義不可で、必ず指定する必要があります。

この2つのアノテーションはエントリポイントとなるクラスに置くのが良いと思います。

最後にこのライブラリの利用方法。
ビルド済みのjarを用意して{projectDir}/libs/MinecraftForgeAnnotations-1.0.jarのように配置します。
build.gradleに以下を追加

//依存関係にMinecraftForgeAnnotationsを追加
dependencies {
    compile files('libs/MinecraftForgeAnnotations-1.0.jar')
}

compileJava {
    //アノテーションプロセッサを指定
    options.compilerArgs += ['-processor', 'net.orekyuu.minecraftanotations.apt.MinecraftAnnotationProcessor', "-Adir=" + file('templates')]
    doLast {
        //コンパイルが終了したらアセットを適切な位置へコピー
        tasks.copyAssets.execute()
    }
}

task copyAssets(type: Copy) {
    from 'build/classes/main/assets/'
    into 'out/production/Minecraft/assets/'
}

-Adirオプションではテンプレートの配置ディレクトリを指定しています。

テンプレートはtemplatesフォルダに配置してください。
動く形になっているサンプルも用意しておいたので良かったら確認して下さい。
サンプルプロジェクト
MinecraftForgeAnnotationsのリポジトリ

バグ報告や要望などはリポジトリのIssueにお願いします。
プルリクエストも待ってます。

APTでModdingわいわい

by orekyuu 0 Comments

1.8のもぢんぐでは、アイテムの追加でjson書かないといけないみたい。
描画の設定を記述するんだけど、使い回しが聞かないのが辛い。
実際書いたjsonだけど、基本的にlayer0の値しか変わらない。
これだけのためにコピペしたりするの面倒じゃない?

{
  "parent": "builtin/generated",
  "textures": {
    "layer0": "sweetmod:items/green_candy_cane" ここしか変更しないのに!
  },
  "display": {
    "thirdperson": {
      "rotation": [0, 90, -35],
      "translation": [0, 1.25, -3.5],
      "scale": [0.85, 0.85, 0.85]
    },
    "firstperson": {
      "rotation": [0, -135, 25],
      "translation": [0, 4, 2],
      "scale": [1.7, 1.7, 1.7]
    }
  }
}

というわけでAnnotationProcessorを作ってみた。

方法については以下を参考にするとわかりやすいと思います。
「Java SE 6完全攻略」第94回 アノテーションを処理する その1
「Java SE 6完全攻略」第95回 アノテーションを処理する その2
「Java SE 6完全攻略」第96回 アノテーションを処理する その3
「Java SE 6完全攻略」第97回 アノテーションを処理する その4
「Java SE 6完全攻略」第98回 アノテーションを処理する その5
「Java SE 6完全攻略」第99回 アノテーションを処理する その6
「Java SE 6完全攻略」第100回 アノテーションを処理する その7

結果
アイテム生成はこんな感じになりました

@Languages({
    @Language(langType = LangType.EN_US, key = "item.RedCandyCane.name", name = "RedCandyCane"),
    @Language(langType = LangType.JA_JP, key = "item.RedCandyCane.name", name = "赤いキャンディーケイン")
})
@ItemModel(itemName = "RedCandyCane", textures = "sweetmod:items/red_candy_cane")
public static ItemCandyCane redCandyCane;

langファイルについても面倒なので一緒にAPTで処理できるようにしてみました。

コンパイルした時のファイルの出力先が/build/classes/main/の中になっていたので、コンパイルタスクが終わったら、ファイルを/out/production/{プロジェクト名}/にコピーするタスクをbuild.gradleに書いておしまい。

使ってみた感じかなり便利。AnnotationProcessorが入ったjarはMODのリポジトリのlibsフォルダに入っているので良かったら使ってみてください。

リポジトリ

おまけ
無題

IntelliJ IDEAで僕だけの最強のBukkitPlugin開発環境を作る

by orekyuu 0 Comments

BukkitプラグインをIntelliJ IDEAでワイワイしてみようと思う。
一応理想はGithubでソースコードを管理してJenkinsでビルドできるようにしたい。

1.Gradleでプロジェクトを作成

f4141f8c9a4f58a15755da42e88da771
JDKは1.7に設定。プロジェクト名はおこのみで。

2.build.gradleを編集

repositoriesを編集

repositories {
    mavenCentral()
    maven {
        url "http://repo.bukkit.org/content/repositories/snapshots"
    }
}

dependenciesにbukkitを追加

dependencies {
    compile 'org.bukkit:bukkit:1.7.2-R0.3-SNAPSHOT'
    testCompile group: 'junit', name: 'junit', version: '4.11'
}

とりあえずこれで開発できる形にはなった。

3.Githubと連携

上部のメニューバーにあるVCSから、Import Into Version Control>Share Project on Githubを選択。
選択するのはこんな感じ。
fb00bbdb5f73ed0325b31da9057323f9

リポジトリが作成されました。
1fdf9441655181dbd77707ea9b479e01

4.Jenkinsで自動ビルド

Jenkinsで新規ジョブを作成する。
予めGithub Plugin・Gradle Pluginのインストールが必要。
フリースタイルで作成。
080ecbc69088a5c4d6d4130b5e7db572

Github projectにGithubのリポジトリを入力。
例:https://github.com/orekyuu/BukkitPluginTutorial/

ソースコード管理をGitにし、Repository URLを入力。
例:https://github.com/orekyuu/BukkitPluginTutorial.git

ビルド・トリガのBuild when a change is pushed to GitHubにチェックを入れる。Githubのリポジトリにpushされるとビルドされるようになる。

ビルド手順を追加
Invoke Gradle scriptを選択。
Invoke Gradleを選択し、インストールしてあるGradleを選択。
Taskにはtestとjarを指定しておく。

ビルド後の処理を追加
成果物を保存する。jarタスクによって作られたjarを成果物として指定する。
jarはbuild/libs/の中に保存されるので、build/libs/*.jarを入れておく。

設定を保存し、ビルドをしてみる。
62fe080f9ad27786de3f300d7c818e90

5.Github側の設定

Githubでリポジトリを開き、Settings > Webhook & Services > Add service
Jenkins(Github plugin)を選択
http://JenkinsのURL/github-webhook/を入力して保存。
これでJenkinsに通知が行くようになる。

6.HelloWorldしてみる。

net.orekyuu.tutorialパッケージを作成し、中にTutorialPlugin.javaを作成する。

package net.orekyuu.tutorial;

import org.bukkit.plugin.java.JavaPlugin;

public class TutorialPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        getLogger().info("Hello World");
    }
}

resources内にplugin.ymlを作成

name: TutorialPlugin
main: net.orekyuu.tutorial.TutorialPlugin
version: 1.0

jarタスクを実行し、build/libs/にあるjarを実際のpluginsフォルダにコピーして動作を確認しよう。
HelloWorldが出力されているのを確認。
d539c981386c37b7c84f472c4d23a855
やった!これでプラグインが作れるぞ!
・・・・・・けどこれ面倒じゃない?

7.Gradleにタスクを追加する

pluginsフォルダにコピーする処理、Gradleさんに任せよう。
プロジェクトフォルダにTestServerフォルダを作成する。
TestServerフォルダにBukkitを起動するrun.batとBukkitのjarを入れておく。

build.gradleに以下を追加

task copyToServer(dependsOn: 'jar', type: Copy) {
    from 'build/libs/'
    into "TestServer/plugins/"
}

copyToServerタスクを実行することで、jarを作成してpluginsフォルダにコピーまですることができる。

8.IDEAからrun.batを起動する

IDEAからrun.batを起動するには、Batch Scripts Supportプラグインを入れるとできる。
実行はProjectからrun.batを右クリックしてRunをするだけだ。
次の起動からはショートカットやメニューバーからの起動が可能だ。
8772dcbe8f5cf8b41ce91e6d8ea2c74b

⑨.完成

c29e8811d6a6b5a1f7832068bce1653e
こんなかんじに動けば成功。やったぜ!
僕が今使っている環境はこんな感じ。
君だけの最強のBukkitプラグイン開発環境を作るのだ!

MinecraftのModding環境の作り方メモ

ここからforgeのsrcをダウンロード

forgeを解凍してコンソールを開く
gradlew setupdev setupdecomp
gradlew idea
IDEAを起動してプロジェクトを開く
build.gradleのversion group archivesBaseNameをそれぞれ適当なものに変更して終了
Forgeのバージョン10.12.1.1060現在JDK8は使えないので注意

Modを配布するためのJarを作成するためには、IDEAでTerminalを開き
gradlew build
ProjectName/build/libs/にjarが作成されます