pmmpの開発環境を無課金で整えて、簡単なプラグインを作ろう！(vscode編)

# イントロ
この記事はMinecraftサーバ開発・運営 Advent Calendar 2021 16日目です。
1285日の遅刻ですよろしくお願いします

pmmp界隈の皆さんお元気にされていますか？私は2025年になっても未だpmmpと戯れている囚人です。
この記事では、課金エディタ(1年間の使用権利 約1万4000円)であるphpstromになるべく近い環境をvscodeで整えるまでの記事です。

まずはこの記事で、ダウンロードフォルダにpmmpをの実行展開、devtool.pharがプラグインフォルダにある状況が手元にあることを前提として解説します。

https://qiita.com/DaisukeDaisuke/items/27a8709c2b545c3b7138


# お断り
:::note info
お断りしますが、課金してphpstrom(1.4万)で開発するほうが構築の面でも、プログラミング上のQOLの面でも、開発ガチるなら圧倒的に楽です。
phpstromには、フォールバックライセンス制度があり、1度でも年間ライセンスを購入すれば特定の過去のバージョンが使い放題になります。なお、ライセンスが有効の間は最新バージョンを使用することができます。
また、学生で、[学生の証明(GitHub Education)](https://zenn.dev/iizuka0000/articles/how-to-apply-for-github-education)ができれば、phpstromなどを無料で使用することができ、卒業時には40% offでフォールバックライセンス付きの購入することができます。
phpstromでの開発編は気が向いたら書きます。
:::

https://www.jetbrains.com/phpstorm/buy/?section=personal&billing=yearly


:::note warn
vscodeでのセットアップは非常に複雑で、非常に厳しい道のりになります。覚悟の上で進んでください。
:::


キーボードショートカット、ctrl+c(コピー)とctrl+v(貼り付け)、ctrl+a(全選択)はすでに習得済みという前提を元に解説します。

# 必要なもの
:::note info
Windows 10 or Windows 11のパソコン
無制限のインターネット回線
ちゃんとループバックしてくれるルーター(ソフトバンクAIRなどはだめ)
:::

# vscodeを入手する

下記からvsocdeをインストールしましょう。
https://code.visualstudio.com/

インストーラーを起動し、ライセンス条約に同意してください。

:::note info
契約という物騒な言葉がかかれていますが、ソフトウェアを使ったり、配布したり、引用したりする際のしていいこと、したらだめなことなどが書かれた文章に同意するだけなので、実際に契約が発生するわけはありません。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/932d6f21-b967-4520-84f0-4761bdeb19a3.png)

お好みに応じて編集します。

> - エクスプローラーのファイルコンテキスト...: ファイルを右クリックした右クリックメニュー(win11の場合詳細表示)に **[codeで開く]を追加**します。
>     - あると非常に便利です。
> - エクスプローラーのディレクトリコンテキスト...: ディレクトリを右クリックした右クリックメニュー(win11の場合詳細表示)に **[codeで開く]を追加** します。
>     - あると非常に便利です。
>     - ないと後々困ります。
> - サポートされてえいるファイルの種類のエディタ...: phpファイルなどのプログラム関連付けを勝手にvscodeに変更します。
> - pathへの追加: git cliなどを使うときに便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/f347894f-1b62-4356-9f2e-838b50d0e6c3.png)

インストールをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/3d74e8ed-bc2c-4235-b8b8-96c638b65931.png)

インストール完了したら、初回起動のためにウィザードを閉じます。
するとこのような画面がでてきますが、いったん閉じましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/b6b3cf9c-5669-4130-a268-219bb37fde41.png)


# composerをダウンロードする

composerとは、phpのパッケージマネージャであり、1つのコマンドでpmmpの依存関係をすべて取得できる優れものです。
下記にpmmpがあることを確認してください。ない場合は下記の記事に従ってインストールをしてください
```
C:\Users\<ユーザー名>\Downloads\PocketMine-MP
```

https://qiita.com/DaisukeDaisuke/items/27a8709c2b545c3b7138

composerフォルダを作成してください。
```
C:\Users\<ユーザー名>\Downloads\PocketMine-MP\composer
```

:::note warn
PocketMine-MPフォルダにbinフォルダがあることを確認してください。ない場合はpmmpをダウンロードフォルダにインストールしてください
:::

次に[ここからcomposer.phar](https://getcomposer.org/download/latest-stable/composer.phar)をダウンロードします。

その後、composerフォルダにコピペしてください
```
C:\Users\<ユーザー名>\Downloads\PocketMine-MP\composer\composer.phar
```


次に、コマンドプロントを開きます。
composerフォルダに移動し、アドレス欄にcmdと入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/181da512-94c6-41d0-ab8f-0b399c8e53df.png)

黒い画面が出ましたか？出たのであれば続行してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/20942835-83ee-4e4a-b621-0ce415e37078.png)


次に、次のコマンドを実行します

```cmd
..\bin\php\php.exe .\composer.phar req pocketmine/pocketmine-mp *
```
こんな感じのメッセージが出れば成功です！
```
C:\Users\ESv87g9gvea4\Downloads\PocketMine-MP\composer>..\bin\php\php.exe .\composer.phar req pocketmine/pocketmine-mp *
./composer.json has been created
Running composer update pocketmine/pocketmine-mp
Loading composer repositories with package information
Updating dependencies
Lock file operations: 22 installs, 0 updates, 0 removals
  - Locking adhocore/json-comment (1.2.1)
  - Locking brick/math (0.13.1)
  - Locking netresearch/jsonmapper (v5.0.0)
  - Locking pocketmine/bedrock-block-upgrade-schema (5.1.0)
  - Locking pocketmine/bedrock-data (5.1.0+bedrock-1.21.90)
  - Locking pocketmine/bedrock-item-upgrade-schema (1.14.0)
  - Locking pocketmine/bedrock-protocol (39.0.0+bedrock-1.21.90)
  - Locking pocketmine/binaryutils (0.2.6)
  - Locking pocketmine/callback-validator (1.0.3)
  - Locking pocketmine/color (0.3.1)
  - Locking pocketmine/errorhandler (0.7.0)
  - Locking pocketmine/locale-data (2.25.1)
  - Locking pocketmine/log (0.4.0)
  - Locking pocketmine/math (1.0.0)
  - Locking pocketmine/nbt (1.1.1)
  - Locking pocketmine/pocketmine-mp (5.30.0)
  - Locking pocketmine/raklib (1.2.0)
  - Locking pocketmine/raklib-ipc (1.0.1)
  - Locking pocketmine/snooze (0.5.0)
  - Locking ramsey/collection (2.1.1)
  - Locking ramsey/uuid (4.8.1)
  - Locking symfony/filesystem (v6.4.13)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 22 installs, 0 updates, 0 removals
  - Installing adhocore/json-comment (1.2.1): Extracting archive
  - Installing netresearch/jsonmapper (v5.0.0): Extracting archive
  - Installing pocketmine/bedrock-block-upgrade-schema (5.1.0): Extracting archive
  - Installing pocketmine/bedrock-data (5.1.0+bedrock-1.21.90): Extracting archive
  - Installing pocketmine/bedrock-item-upgrade-schema (1.14.0): Extracting archive
  - Installing ramsey/collection (2.1.1): Extracting archive
  - Installing brick/math (0.13.1): Extracting archive
  - Installing ramsey/uuid (4.8.1): Extracting archive
  - Installing pocketmine/binaryutils (0.2.6): Extracting archive
  - Installing pocketmine/nbt (1.1.1): Extracting archive
  - Installing pocketmine/math (1.0.0): Extracting archive
  - Installing pocketmine/color (0.3.1): Extracting archive
  - Installing pocketmine/bedrock-protocol (39.0.0+bedrock-1.21.90): Extracting archive
  - Installing pocketmine/callback-validator (1.0.3): Extracting archive
  - Installing pocketmine/errorhandler (0.7.0): Extracting archive
  - Installing pocketmine/locale-data (2.25.1): Extracting archive
  - Installing pocketmine/log (0.4.0): Extracting archive
  - Installing pocketmine/raklib (1.2.0): Extracting archive
  - Installing pocketmine/raklib-ipc (1.0.1): Extracting archive
  - Installing pocketmine/snooze (0.5.0): Extracting archive
  - Installing symfony/filesystem (v6.4.13): Extracting archive
  - Installing pocketmine/pocketmine-mp (5.30.0): Extracting archive
3 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
4 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
No security vulnerability advisories found.

C:\Users\ESv87g9gvea4\Downloads\PocketMine-MP\composer>
```


# vscodeをカスタマイズする。

### プラグインをインストールする

windowsの検索バー等に「code」と入れ、vscodeを起動します。

こんな感じの画面が出ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/eb233170-ee2f-4a96-8b11-f33e35532a65.png)

次にここをクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/c4490a4f-63a4-4d92-8123-46ba8f1dc526.png)

こんな感じの画面になると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/8d0b33fa-a83e-467d-9710-e0af22919011.png)

ここに下記の文字を入力します。
```
PHP Intelephense
```
これはメインのランゲージ(補完)サーバーです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/a72fe525-5d6c-4010-b7d4-0cff466c97bf.png)


ここをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/3be6f1a9-8812-43d8-b42d-cdd4b6c5da5c.png)

表示されたウインドウ内で、「インストール」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/c0b33d2d-a3b3-4196-acbc-8ccc09efcf63.png)

次に入力された文字を削除し、
```
PHP DocBlocker
```
を入力します。

これはphpdocを書くための補助ツールです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/d0bb6613-affb-4cc1-8fdf-96daea2ab648.png)

次に入力された文字を削除し、
```
PHP import checker
```
を入力します。
これは未使用のuse文などをハイライトしてくれる機能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/cb567702-c6df-478b-b43e-a28233f63b9c.png)

### 組み込みの補完機能を無効化する
:::note warn
画像では無効化してますが、誤りであるため、
PHP Language Basicsは無効化しないでください
:::

vscodeに組み込まれた必要最低限のphp補完機能を無効します。無効化しないと悲しいことが起きるのでスキップしないでください。

次に入力された文字を削除し、次の文字を入力します。

```
@builtin php
```
PHP Language Featuesをクリックします。

Disableをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/a9f685c7-9ecd-44af-bc73-e90da89d5800.png)



### 日本語化する

入力された文字を削除し、
```
日本語
```
と入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/0ebb2000-467b-4ad7-82da-5a1b84e3eb69.png)

告知ポップアップから、change language and restartを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/10e8e678-2faa-4727-8127-3934bdedc5e8.png)




## 設定を編集する
このままではオートセーブなどが無効なので有効にします。

歯車マーク、settingから設定を有効化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/651c86f3-45e2-4407-8c1a-d90d4eaf9eeb.png)

開いたら、このボタンを正確にクリックします


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/f2054e1b-aa40-4b15-88c3-6dd14e46f373.png)

こんな感じの画面になったら成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/bfb71142-78cc-470d-90e9-d36b0366bfdc.png)


内容物をすべて削除し、次のようにします。

:::note info
`ユーザー名`はパソコンのユーザー名に置換してください
日本語ユーザー名の場合対応していない可能性があります。
:::

by https://github.com/NeiroNetwork/IDE_Configs/blob/main/.vscode/settings.json

```json
{
    "files.autoSave": "afterDelay",
    "php.validate.executablePath": "C:\\Users\\ユーザー名\\Downloads\\PocketMine-MP\\bin\\php\\php.exe",
    "files.associations": {
        "*.module": "php"
    },
    "php.validate.run": "onType",
    "intelephense.environment.includePaths": [
        "C:\\Users\\ユーザー名\\Downloads\\PocketMine-MP\\composer\\vendor"
    ],
    "php.suggest.basic": false,
    "editor.insertSpaces": false,
    "[yaml]": {
		"editor.insertSpaces": true,
		"editor.tabSize": 2
	},
    "[json]": {
		"editor.insertSpaces": true,
		"editor.tabSize": 4
	},
	"[php]": {
		"editor.tabSize": 4
	}
}
```


### オプションの設定
日本語などを絶対黄色くしない設定(非推奨)
`}`の前に追加します。
```json
    "editor.unicodeHighlight.ambiguousCharacters": false,
    "editor.unicodeHighlight.invisibleCharacters": false,
    "editor.unicodeHighlight.includeComments": false,
    "editor.unicodeHighlight.includeStrings": false,
    "editor.unicodeHighlight.nonBasicASCII": false,
```

こんな感じになれば成功です。

:::note warn
ユーザー名は編集しましたか？
:::


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/06b1c4ec-dabc-466c-910b-f02088be9c14.png)

そしたらctrl+sを1回押して、次の作業をします。

# コードスニペットを編集する
phpstromに近づけるために、vscodeにphpのコードスニペットを追加します。

歯車アイコン→スニペットを選択します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/1fcf6703-acb7-4a21-a8a4-e21b74d0256a.png)

表示された入力欄にphp、[エンター]と入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/1e0731e5-1318-436f-9504-24d8abd67dc7.png)

こうなれば成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/f60663f7-2077-4cdc-badc-e101082230b2.png)

下記のコードスニペットをすべてコピーし、上書き貼り付けしてください

```json
{
	// Place your snippets for php here. Each snippet is defined under a snippet name and has a prefix, body and 
	// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
	// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the 
	// same ids are connected.
	// Example:
	// "Print to console": {
	// 	"prefix": "log",
	// 	"body": [
	// 		"console.log('$1');",
	// 		"$2"
	// 	],
	// 	"description": "Log output to console"
	// }
	"if": {
		"prefix": "if_tab",
		"body": [
			"if($1){",
			"\t",
			"}",
		],
	},
	"foreach": {
		"prefix": "foreach",
		"body": [
			"foreach($1 as \\$key => \\$value){",
			"\t$2",
			"}",
		],
	},
	"<?php": {
		"prefix": "<?php",
		"body": [
			"<?php",
		],
	},
	"<?php class": {
		"prefix": "<?php",
		"body": [
			"<?php",
			"",
			"declare(strict_types=1);",
			"",
			"namespace $1;",
			"",
			"class $2{",
			"\t$3",
			"}",
		],
	},
	"break;": {
		"prefix": "break;",
		"body": [
			"break;",
		]
	},
	"__construct": {
		"prefix": "__construct",
		"body": [
			"__construct",
		]
	},
	"__construct fun": {
		"prefix": "__construct",
		"body": [
			"public function __construct($2){",
			"\t$3",
			"}",
		]
	},
	"fun": {
		"prefix": "fun",
		"body": [
			"public function $1($2){",
			"\t$3",
			"}",
		]
	},
	"var_dump();": {
		"prefix": "var_dump",
		"body": [
			"var_dump($1);",
		]
	},
	"info": {
		"prefix": "info",
		"body": [
			"\\$this->getLogger()->info($1);",
		]
	},
	"static": {
		"prefix": "static",
		"body": [
			"static",
		]
	},
	"extends": {
		"prefix": "ex",
		"body": [
			"extends ",
		]
	},
	"extends1": {
		"prefix": "extends",
		"body": [
			"extends ",
		]
	},
	"instanceof": {
		"prefix": "instanceof",
		"body": [
			"instanceof",
		]
	},
	"instanceof1": {
		"prefix": "ins",
		"body": [
			"instanceof",
		]
	},
	"public": {
		"prefix": "public",
		"body": [
			"public ",
		]
	},
	"return": {
		"prefix": "return",
		"body": [
			"return ",
		]
	},
	"true": {
		"prefix": "true",
		"body": [
			"true",
		]
	},
	"false": {
		"prefix": "false",
		"body": [
			"false",
		]
	},
	"private": {
		"prefix": "private",
		"body": [
			"private",
		]
	},
	"protected": {
		"prefix": "protected",
		"body": [
			"protected",
		]
	},
	"pubf": {
		"prefix": "pubf",
		"body": [
			"public function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
	"pubsf": {
		"prefix": "pubsf",
		"body": [
			"public static function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
    "prof": {
		"prefix": "prof",
		"body": [
			"protected function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
	"prosf": {
		"prefix": "prosf",
		"body": [
			"protected static function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
	"prif": {
		"prefix": "prif",
		"body": [
			"private function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
	"prisf": {
		"prefix": "prisf",
		"body": [
			"private static function $1($2): ${3|void,int,string,array,bool,mixed|}{",
			"\t$4",
			"}",
		]
	},
	"thr": {
		"prefix": "thr",
		"body": [
			"throw new $1"
		]
	},
	"thrRuntimeException": {
		"prefix": "RuntimeException",
		"body": [
			"throw new \\RuntimeException(\"\");"
		]
	},
	"continue": {
		"prefix": "continue",
		"body": [
			"continue;",
		]
	},
	"@phpstan-var": {
		"prefix": "@phpstan-var",
		"body": [
			"@phpstan-var $1",
		]
	},
	"@phpstan-return": {
		"prefix": "@phpstan-return",
		"body": [
			"@phpstan-return $1",
		]
	},
	"@phpstan-param": {
		"prefix": "@phpstan-param",
		"body": [
			"@phpstan-param $1",
		]
	},
	"@phpstan-var array<>": {
		"prefix": "@phpstan-var array<>",
		"body": [
			"@phpstan-var array<$1, $2>",
		]
	},
	"@phpstan-return array<>": {
		"prefix": "@phpstan-return array<>",
		"body": [
			"@phpstan-return array<$1, $2>",
		]
	},
	"@phpstan-param array<>": {
		"prefix": "@phpstan-param array<>",
		"body": [
			"@phpstan-param array<$1, $2>",
		]
	},
	"@phpstan-var list<>": {
		"prefix": "@phpstan-var list<>",
		"body": [
			"@phpstan-var list<$1>",
		]
	},
	"@phpstan-return list<>": {
		"prefix": "@phpstan-return list<>",
		"body": [
			"@phpstan-return list<$1>",
		]
	},
	"@phpstan-param list<>": {
		"prefix": "@phpstan-param list<>",
		"body": [
			"@phpstan-param list<$1>",
		]
	},
	"declare(strict_types=1); 2":{
		"prefix": "declare",
		"body": [
			"declare(strict_types=1);"
		]
	},
	"declare(strict_types=1);":{
		"prefix": "decl",
		"body": [
			"declare(strict_types=1);"
		]
	},
	"onCommand": {
		"prefix": "onCommand",
		"body": [
			"public function onCommand(CommandSender $$sender, Command $$command, string $$label, array $$args) : bool{",
			"\tswitch($$label){",
			"\t\tcase '$1':",
			"\t\t\t$2",
			"\t\t\tbreak;",		
			"\t}",
			"}",
		]
	},
	"re": {
		"prefix": "re",
		"body": [
			"return $1;",
		]
	},
	"private ": {
		"prefix": "pr",
		"body": [
			"private $1",
		]
	},
  "foreach ($array as $value)": {
    "prefix": "fore",
    "body": [
      "foreach (${1:\\$array} as ${2:\\$value}) {",
      "\t$0",
      "}"
    ],
    "description": "PHP foreach over values"
  },
  "foreach ($array as $key => $value)": {
    "prefix": "forek",
    "body": [
      "foreach (${1:\\$array} as ${2:\\$key} => ${3:\\$value}) {",
      "\t$0",
      "}"
    ],
    "description": "PHP foreach over keys and values"
  },
	"for ($i = 0; $i < count($array); $i++)": {
	"prefix": "fori",
	"body": [
		"for (\\$${1:i} = ${2:0}; \\$${1} < ${3:count(\\$array)}; \\$${1}++) {",
		"\t$0",
		"}"
	],
	"description": "PHP for loop with editable start index"
	}
}
```

こうなれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/b7fb3018-f7d8-4f65-a181-1adc072d0eec.png)



そしたらctrl+sを1回押してから閉じます。これで、プラグイン制作環境のほぼ90%が完了しました。
お疲れ様でした。
いったんvsocdeは閉じてokです。


# プラグインテンプレートの生成

1記事目の方法で、start.cmdを起動し、サーバーを待機状態にします。
次に、コンソールに次のコマンドを入力します。
これはDevToolsプラグインによる機能です。

:::note info
自分の名前等に編集してください
:::

```
genplugin プラグインの名前 プラグインの製作者
```

次のようになれば成功です！
```
genplugin MyFirstPlugin DaisukeDaisuke
Command output | Created skeleton plugin MyFirstPlugin in C:\Users\ESv87g9gvea4\Downloads\PocketMine-MP\plugins\MyFirstPlugin\
```

サーバーをctrl+cまたはstopコマンドで終了してください。

:::note info
次のようになった場合は、DevTools.pharを導入する必要があります
```
genplugin MyFirstPlugin DaisukeDaisuke
Command output | Unknown command: genplugin. Use /help for a list of available commands.
```

### エラー時の対処法
1. サーバーを停止します
2. [ここからDevTools.phar](https://github.com/pmmp/DevTools//releases/latest/download/DevTools.phar)をダウンロードします。
3. ダウンロードフォルダから`C:\Users\ユーザー名\Downloads\PocketMine-MP\plugins\DevTools.phar`になるようにコピーします。
4. サーバーを起動します。
5. 再度コマンドを試します。
:::

`PocketMine-MP\plugins\MyFirstPlugin\`にプラグインのスケルトンが生成されているので、右クリックメニューから**codeで開く**を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/fc39ea0e-4d2f-453f-bfe2-c5762c29bf15.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/3bf4a28d-aff5-446e-a29c-12393bff6509.png)


次のような画面になれば成功です！

「はい、作成者を信頼します」をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/d4ca0240-7e94-44c0-aeaa-8348ab9a0585.png)

ファイルブラウザを開き、src→main.phpをクリックして、プラグインのメインクラスファイルを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/4ecbb7ca-05de-4a5b-870b-34c579898045.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/a943d6e0-c271-4feb-8575-47b4fb1127f0.png)



次の画面になれば正常にエディタを開けています！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/72e9a87a-8e28-481f-8e04-c1cd8b3b14a2.png)

:::note info
Q: エディタで文字に色がつかないんだけど

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/36db2007-7abe-4ad7-b38f-899ecae0b657.png)

A: 
PHP Language Basicsを間違えて無効化しちゃってます。
拡張機能の検索欄に`@builtin php`と入力して必ず無効(**有効の状態**)になってるか確認してください

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/d1417bdb-871a-4805-8693-8fd415228db4.png)
:::


# 簡単なプラグインを作る

### 起動時にメッセージをコンソールに送るプラグイン

今はこのようになっていると思います。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{

}

```

次にpubfと手動で入力します。すると候補が出てくると思います。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    pubf
}

```

するとスニペットが展開されるので、大文字小文字間違えず1文字も間違えずににonEnableを入力します。
次にキーボードのtabキーを3回押します。するとカーソル位置がかっこの中に移動します。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        //カーソル
    }
}
```


次に、`$this->`と入力しましょう。すると候補が出ます。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->
    }
}

```
次に`getL`と入力してみましょう。するとgetLoggerが出るので、途中で入力をやめて、エンターtabを押しましょう！
すると補完されます！

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()
    }
}
```

次に、`->in`を入力してみましょう！infoが補完されました！


```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()->info()
    }
}

```

次に、かっこの中に`"`を2つ入力しましょう！

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()->info("")
    }
}

```

`"`の間に、好きなメッセージを入れてみましょう！
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)")
    }
}

```

関数呼び出しには忘れてはならないものがあります。行の終わりを表す、`;`です。
今は`;`がないので、読み込むとパースエラーが発生します。なので追加する必要があります。

キーボードまたはマウス、またはendで、行の終わりに移動し、`;`を追加します。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
    }
}

```

これでもっとも単純なプラグインの完成です！

早速1枠目の方法でサーバーを起動しましょう！

こんな文章が出力されれば成功です！
```
[02:55:53.413] [Server thread/INFO]: Enabling MyFirstPlugin v0.0.1
[02:55:53.414] [Server thread/INFO]: [MyFirstPlugin] 私の最初のプラグイン(大嘘)
```

### 棒をたたくとメッセージを送るプラグイン

やはりpmmpは機能は少なくともゲームサーバーなので、ゲーム内に干渉したいですよね！
こんどは棒をたたくとプレーヤーにメッセージを送るプラグインを作りましょう！

まず、棒をたたいたことを検知するために、イベントの事前登録が必要です。
つまり、pmmpに「私イベント受け取れてこんなイベントの処理をもってますよ？」って報告しないと発生したイベントのシグナルを分けてくれないといった感じです。



まず、登録を行うためにはプラグインの拡張を行う必要があります。
この手順はpmmpで開発する上では、何回も行うことになるので、呪文として暗記した方がいいです。

次のコードがあるとします。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
    }
}
```

`class Main extends PluginBase{`にの`e`と`{`の間に移動し、`<スペース>imp<エンター>`と入力します。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
    }
}
```
そのまま`<スペース>List<エンター>`を入力します。
すると自動でuse文が追加され、(イベントの)Listenerであることを宣言することができました。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
    }
}
```

ただ、これだとオレオレlistenerのように自称してるだけなので、pmmpに`私イベント受け取れますよ`と報告しましょう。
`$this->getLogger()->info("私の最初のプラグイン(大嘘)");`の最後文字に移動し、キーボードのエンターをクリックします。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        <カーソル>
    }
}
```
`$this->`と入力します。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this-><カーソル>
    }
}
```
`getSer<エンター>->`と入力し、エンターを押します。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->
    }
}
```

`getPlug`と入力し、**2番目の**`getPluginManager`を選びます。
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()
    }
}
```

`->r`と入力し、`registerEvents`を選びます。**sがある方です。**

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents()
    }
}
```

かっこ内に移動し、`$this, $this`を入力します。呪文です。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this)
    }
}
```

最後に関数呼び出しなので、`;`を文末に追加します。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }
}
```

サーバーを起動し、エラーを吐かないこと(赤いメッセージ)を確認します。
これでちゃんとpmmpにイベントを受け取れることを報告できたので、実際のイベントの処理部や、イベントの種類を指定、追加します。

:::note info
このコードで発生しうるエラーを一部紹介

Q: 
```
 ParseError: "syntax error, unexpected token "}"" (EXCEPTION)
 syntax error, unexpected token "{"
```
A: 
`;`をちゃんと追加したか確認してください
`implements`の実装途中でやめてませんか？

Q: 
```
ParseError: "Unclosed '{' on line 10" (EXCEPTION)
```
A: `}`を間違って消しちゃってます。`}`を適当に追加するのはやめましょう。事故の原因です。

Q:
```
ArgumentCountError: "Too few arguments to function pocketmine\plugin\PluginManager::registerEvents(), 1 passed in C:\Users\ESv87g9gvea4\Downloads\PocketMine-MP\plugins\MyFirstPlugin\src\Main.php on line 13 and exactly 2 expected"
```
A: `$this`の数が少ないです。


Q: 
```
ParseError: "syntax error, unexpected token "public", expecting "{"" (EXCEPTION)
```

A: `{`がないです

Q:
```
ParseError: "Unmatched '}'" (EXCEPTION) 
```
A: `}`が多すぎます

Q:
```
TypeError: "pocketmine\plugin\PluginManager::registerEvents(): Argument #1 ($listener) must be of type pocketmine\event\Listener, DaisukeDaisuke\MyFirstPlugin\Main given, called in C:\Users\ESv87g9gvea4\Downloads\PocketMine-MP\plugins\MyFirstPlugin\src\Main.php on line 13" (EXCEPTION)
```
A: これがない
```
 implements Listener
```
:::

### イベントを実装

その前に前提知識をいつくか。

:::note info
Playerオブジェクト
- pmmpでプラグイン開発をする上で、必ず出てくるphpオブジェクトです。
- プレーヤーオブジェクトは1個が1人のオンラインプレーヤーに割り当てられています。
- phpのオブジェクトの都合上、どんなにコピーしても同じプレーヤーなら同じplayerオブジェクトが取得されます。
- プレーヤーオブジェクトを配列等に格納した場合、勝手にnullになることはありません。
- ただし、プレーヤーがログアウトしてオフラインになるとプレーヤーオブジェクトは大半の機能を失い、`$player->isOnline()`のみ安全に実行することができます。それ以外は基本サーバがクラッシュします。
- `$player->isOnline()`は最終手段であり、基本的にはプラグインは`PlayerQuitEvent`でプレーヤーへの参照を消します。
- なお、基本ユーザーが再ログインすると機能を失ったプレーヤーオブジェクトがもとに戻るとは考えない方がいいです。イベント等から再取得する必要があります。
:::


:::note info
イベントシステム

- イベントの定義は特定のルールの関数の定義によって行われます。
- イベントの登録をしていないクラスが勝手に呼ばれるといった超能力的なことはありません。
- **イベントは同時実行されることはありません**
    - 同時実行ガードは不要です。
    - 2つのイベントを定義した場合、実質ランダムな順序で順番に実行されます。
    - イベントには優先度があり、所定の方法で優先度を上げたり下げたりすることで**実行順番を制御する方法も用意** されています。
- `$this->getServer()->getPluginManager()->registerEvents($this, $this);`は呪文です。
- イベントオブジェクトは、各イベントによってさまざまな関数を持っています。
:::


現在このようなコードがあります。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }
}
```

関数を定義したいので、クラスのカッコの一番下で改行します。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }
    <カーソル>
}
```
`pubf`と入力します。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }
    pubf
}
```

1つ目は適当にします。tabを押し、

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }

    public function a(): void{
        
    }
}
```

2つめはイベント名を入れます。
今回は、アイテムを空中タップしたときのイベントが欲しいので、`PlayerItemUseEvent`を使います
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerItemUseEvent;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }

    public function a(PlayerItemUseEvent): void{
        
    }
}
```

<スペース>$eventと入れます。

```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerItemUseEvent;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }

    public function a(PlayerItemUseEvent $event): void{
        
    }
}
```

これがイベントの基本形です！

このイベントがどんな関数を持ってるかわからないので、pmmpのソースコードを見に行きましょう。
**ctrlを押しながら** 、`PlayerItemUseEvent`を **右クリック** します。
するとソースコードに飛びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/e809ccd8-0b6e-4853-b773-5483cc20c5a4.png)

どうやらアイテムを取得できるようですね！

`PlayerEvent`が気になるので、見に行きましょう！
**ctrlを押しながら** 、`PlayerEvent`を **右クリック** します。

どうやらgetPlayerという関数があるようですね！いかにもplayerオブジェクトを取得できそうな名前です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/f37118fc-68b7-4c4e-9cf8-1818f2bf3685.png)

ソースコードから情報を得たので、Main.phpに戻り、プレーヤーオブジェクトを取得してみましょう！

カーソル位置をここに合わせます。
```php
    public function a(PlayerItemUseEvent $event): void{
        <カーソル>
    }
```

`$player = `と入力します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = 
    }
```

PlayerItemUseEvent、変数名$eventからプレーヤーを取得しましょう。
`$e<エンター>->`と入力します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->
    }
```

先ほど調べた`getPlayer`関数を呼び出しましょう！
`getP<エンター>`と入力します。

```php
   public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer()
    }
```

関数呼び出しの終わりには`;`が必要なので、追加します。
```php
   public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
    }
```
メッセージを送ってみましょう。メッセージを送る関数は`(Player)->sendMessage()`です！

改行し、`$p<エンター>`と入力します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player
    }
```
`->sendm`を入力します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player->sendMessage()
    }
```

sendメッセージには文字列が必要なので、`""`を入力します。

```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player->sendMessage("")
    }
```

すきなメッセージを入力します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player->sendMessage("pmmp好きな処理発表ドラゴン")
    }
```
関数呼び出しの終わりには`;`が必要なので、追加します。
```php
    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player->sendMessage("pmmp好きな処理発表ドラゴン");
    }
```

複数のメッセージを送りたい場合は、複数回呼び出します！
文字の変数展開をしたくない場合は、`7`と同じ位置にある、`'`これで囲みます。`@`の位置にあるやつ「\`」で囲むとコマンドが実行されて**最悪パソコンが壊れるので絶対しないで**ください
```php
<?php

declare(strict_types=1);

namespace DaisukeDaisuke\MyFirstPlugin;

use pocketmine\event\Listener;
use pocketmine\event\player\PlayerItemUseEvent;
use pocketmine\plugin\PluginBase;

class Main extends PluginBase implements Listener{
    public function onEnable(): void{
        $this->getLogger()->info("私の最初のプラグイン(大嘘)");
        $this->getServer()->getPluginManager()->registerEvents($this, $this);
    }

    public function a(PlayerItemUseEvent $event): void{
        $player = $event->getPlayer();
        $player->sendMessage("pmmpの好きな処理発表ドラゴン");
        $player->sendMessage("1位: \Closure::bind(fn() => なんか, \$packet, \$packet)();");
        $player->sendMessage('2位: HandlerListManager::global()->unregisterAll($listener);');
        $player->sendMessage('3位: $this->getScheduler()->scheduleRepeatingTask(new ClosureTask(function() : void{}), 20);');
    }
}
```



サーバーを起動し、opを付与し、ゲームモードを1にしてアイテムを空中クリックすると、メッセージが送信されます！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192565/caef9408-fc53-4e9f-92ad-495e68b2cff6.png)

:::note warn
サーバーに同じパソコンのマインクラフトwin10エディションから入れない場合、インストーラーの管理者権限を2つとも許可したかどうか確認してください
:::


この記事は以上です、お疲れさまでした。
次回は、数か月後、第3回企画、phpstromセットアップ編、第4回企画、すぐにコピペできるコード編、(未定)、上級者向けテクニック編でお会いしましょう。
それではよいソースコード漁りライフを！


# さいごに

### コードスニペット紹介

pubf: public function
pubsf: public static function
prof: protected function
prosf: protected static function
prif: private function 
prisf: private static function
thr: throw new

### エディタが参照するソースコードの更新

次のフォルダに移動します。
```
C:\Users\<ユーザー名>\Downloads\PocketMine-MP\composer
```
エクスプローラーのアドレスバーに`cmd`と入れ、コマンドプロンプトをこのフォルダで起動します。
次のコマンドを実行します。
```
..\bin\php\php.exe .\composer.phar upd
```
コマンドが完了すれば、エディタの参照するソースコードが更新済みです。
場合によってはvsocdeの再起動も必要です

### 次の記事を待ちきれない場合
`server.mcbe.jp`でpmmp歴8年自称プロである私がサポートします。
https://server.mcbe.jp/