Plugin Tutorial/ko

소개
--

이 방대한 양의 튜토리얼은 당신이 버킷의 플러그인을 제작할 수 있도록 도와주는 데에 초점을 맞추었습니다. 이 튜토리얼이 버킷에 대해 모든 내용을 담지는 않지만, 기본적이고 일반적인 웬만한 내용의 모든 것을 담고 있습니다. 참고로 이 강좌는 여러분이 자바(Java)를 알고 있다는 가정하에 진행됩니다. 튜토리얼을 보기 전에, 통합개발환경(Integrated Development Environment, 예: Eclipse등..)에서 당신의 개발환경(workspace) 설정을 마치고나서 아래 내용을 보시기 바랍니다.

## Java 배우기

**버킷 튜토리얼들을 보려면 자바에 대한 기초지식이 요구됩니다. 아직 자바를 배우지 않았거나 잘 알지 못하면 아래 링크에서 자바를 배워보세요!**

Oracle's Java Tutorials - 공식 강좌

* Java2s.com - 강좌
* Java 101 - 강좌
* JavaNotes - 무료 온라인 책
* TheNewBoston - 비디오 강좌

## 개발 환경

다시 한번 강조하지만, 플러그인을 개발하기 전에(또는 자바를 배우기 전에) 개발 환경을 꼭 설정해야 합니다! IDE(Integrated Development Environt) 외에도 사용이 가능합니다. 이 강좌는 이클립스 IDE를 기준으로 설명합니다.

*자세한 내용은 Setting Up Your Workspace/ko를 참고하십시오.*
## 플러그인 프로젝트 시작

### 프로젝트 만들기

시작하기 전에, 이클립스의 작업공간 (workspace)과 파일들을 설정해야 합니다. 이클립스를 실행하고 *File > New > Java Project:*(한글: *파일 > 새로 만들기 > 자바 프로젝트:*)를 선택하여 새 프로젝트를 만드세요.

*Maven*폴더를 펼치고 *Maven Project*를 선택하세요. next를 클릭한 후 나오는 페이지에서 *Create a simple project* 박스에 표시를 하세요. 다시 *Next*를 클릭하세요 : *Maven* 폴더가 안보이면 이곳에서 m2eclipse 플러그인을 다운로드받아야 합니다.

이제, 다음과 같이 그룹명을 붙입니다.:

* 가지고 있는 도메인이 있으면 도메인이름의 역순으로 패키지를 만드세요.
	+ 예: 도메인이 i-am-a-bukkit-developer.com 이면 패키지는 com.i\_am\_a\_bukkit\_developer 와 같이 될겁니다. source
	+ 당신이 가지지 않은 도메인을 사용하는건 피하세요.
* 가지고 있는 도메인이 없나요? 아래에 몇가지 관례가 있습니다.
	1. GitHub나 sourceforge같은 소스 관리 사이트에 가서 계정을 하나 만드세요.
		+ GitHub의 경우 여기 적힌대로 하시면 됩니다. 그러면 하위 도메인을 갖게되고 패키지는 io.github.<username> 와 같을겁니다.
	2. 이메일 주소를 이용해도 됩니다. 예: <username>@gmail.com 이 있으면 패키지는 com.gmail.<username> 이 될겁다.♙
	3. 단순히 유일한 그룹명(아무렇게나)을 붙이는건 그다지 바람직하지 않으니 최후의 수단으로 사용하세요.

그룹명으로 사용해서는 *안되는* 몇가지 이름들은 아래와 같습니다.

* org.bukkit
* net.bukkit
* com.bukkit
* net.minecraft

일단 기본 그룹명을 정하면 플러그인 이름을 정해서 끝내야 할겁니다. GitHub 에서 계정을 만들었다고 하고 예를 들어보겠습니다. 당신이 *TestPlugin*이라는 플러그인을 생성하고 있으면 그룹명은 *io.github.<username>*이고 artifact명은 *TestPlugin* 일겁니다. 버전의 경우 지금은 간단히 기본값으로 고정해둡니다. 나중에 바뀔 수 있습니다.

창 끝내기:

이클립스를 처음 사용하는 중이면 Welcome 탭에서 "X" 를 클릭해서 닫읍시다. 이제 이클립스 창은 아래와 같은 모습이 될겁니다.

artifact명(프로젝트명) 왼쪽에 있는 화살표를 클릭하세요. 이제 시작할 준비가 다 됐습니다!

### Bukkit API 참조 설정하기

플러그인 개발을 시작하기 전에 버킷 API 라이어브러리를 당신의 프로젝트에 외부 JAR로 추가할 필요가 있습니다. 사용하고 싶은 다른 API들이 있으면 추가할 수 있습니다.

버킷 API 최신 컴파일 jar 는 이곳에서 다운받을 수 있습니다. (~~Bukkit API - Development Snapshot~~)현재 이곳에서는 다운로드받을 수 없습니다.

프로젝트 폴더의 맨 아래에 있는 *pom.xml* 을 더블 클릭합니다. 아래와 같은 것을 볼 수 있을겁니다.

자바6 이상의 버전을 사용하고 싶다면 프로젝트를 빌드할때 적용될 자바 버전을 지정해줘야 합니다. 아래 코드를 복사해서 *</project>* 바로 위에 붙여넣으세요.(아래 코드는 자바 7 버전 이하에서 빌드되어야 한다는 것을 나타냅니다.)

````
   <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                  <source>1.7</source>
                  <target>1.7</target>
              </configuration>
          </plugin>
      </plugins>
   </build>

````

자바 버전 8에 해당하는 1.8을 적용할 수도 있습니다. 여기 according to MCStats을 보시면 서버 소유자들의 많은 수가 자바 버전 7을 사용하고 있음을 알 수 있습니다. 따라서 자바 버전 8에 맞춰서 플러그인을 컴파일하면 대부분의 서버 소유자들은 당신의 플러그인을 사용할 수 없을겁니다. 자바7의 특징들을 사용하면 "error" 표시가 난 코드 위로 마우스를 가져갈때 이클립스에서는 자바 언어 레벨을 변경하라고 제안할 겁니다.

맨 아래에 *</project>* 이전에 아래 코드를 복사해서 붙여넣으세요.( Bukkit 저장소가 어디에 있는지를 이클립스에게 알려주는 코드입니다.

````
   <repositories>
       <repository>
           <id>bukkit-repo</id>
           <url>http://repo.bukkit.org/content/groups/public/</url>
       </repository>
   </repositories>

````

그 다음, 다시 *</project>* 바로 앞에 아래 코드를 복사해서 붙여넣습니다.( 만드는 플러그인이 Bukkit의 어느 버전에 맞춰 빌드 되는지 나타냅니다.)

````
   <dependencies>
       <dependency>
           <groupId>org.bukkit</groupId>
           <artifactId>bukkit</artifactId>
           <version>1.6.4-R2.0</version>
           <type>jar</type>
           <scope>provided</scope>
       </dependency>
   </dependencies>

````

원한다면 Bukkit 버전을 바꿀 수도 있습니다. 채용 가능한 버전들은 여기 에서 볼 수 있습니다.

여기까지 다 끝내면 *pom.xml* 은 아래와 같은 모습일겁니다.

*File -> Save* 나 `Ctrl + S` 눌러서*pom.xml* 을 저장하세요. 그리고 프로젝트를 마우스 오른쪽 클릭하고 *Maven -> Update Project* 를 클릭하세요.

### 버킷 javadoc

이클립스와 자바에 어느정도 경험이 있으면 클래스나 메소드 위로 마우스를 가져갈때 클래스(메소드)에 대한 문서가 담겨있는 노란색 상자가 나타나는 것을 알겁니다. Javadoc이라는 것인데 오라클 웹사이트에서 접속할 수도 있습니다.

Bukkit 또한 API에서 제공하는 메소드와 클래스에 대한 유용한 설명을 담고 있는 문서를 제공하고 있고, 이곳에서 사용 가능합니다. 이클립스에서 Bukkit의 메소드나 클래스에 마우스를 가져갈때 이 정보가 나오도록 하기 위해서는 프로젝트 익스플로러Project Explorer 에서 "Maven Dependencies" 를 펼쳐서 보이는 Bukkit의 jar 파일(bukkit-x.x.x-Rx.x.jar)을 마우스 오른쪽으로 클릭합니다. 그리고 "Properties"를 선택합니다. 팝업창의 왼쪽에 *Javadoc Location* 아이템을 선택하고 **http://jd.bukkit.org/apidocs/** url 을 "Javadoc URL" 아래의 텍스트박스 안에 붙여넣습니다. 아래 그림과 같습니다.

"Validate"를 클릭하고 "OK"를 클릭합니다. 됐습니다! 이제 Bukkit Javadoc 문서가 Bukkit source와 연결되었고 이클립스 안에서 유용한 문서들에 접속할 수 있습니다.

### 패키지 만들기

이번엔 우리가 사용할 모든 자바 클래스들이 저장되는 '패키지'라는 것을 생성할 필요가 있습니다. *src/main/java*라는 이름의 폴더를 마우스 오른쪽 클릭하고 *New > Package* 를 선택합니다.

패키지 이름의 경우 그룹명 다음에 점(.)을 붙이고 artifact명을 소문자로 고쳐서 붙입니다. 예를 들어서 그룹명이 *io.github.name* 이고 artifact명이 *TestPlugin* 일 경우, 당신의 패키지명은 *io.github.name.testplugin* 와 같이 됩니다.

### 플러그인의 클래스 만들기

이제 프로젝트를 설치했으니 클래스 파일을 추가하고 플러그인을 만들 준비가 됐습니다. 플러그인의 메인 클래스는 JavaPlugin이라는 클래스를 상속합니다. 여러분이 만들 플러그인에는 직접적으로든 간접적으로든 JavaPlugin 클래스를 상속하는 클래스가 단 하나만 존재해야 합니다. 메인 클래스를 제일 먼저 만들면서 여러분이 만들려는 플러그인와 같은 이름을 지어주는 것이 좋은 습관입니다. 방금 전에 만든 패키지 이름을 마우스 오른쪽으로 클릭하고 *New > Class* 를 선택합니다.

다음과 같은 새로운 클래스가 만들어질겁니다.

>
> ```
> package {$TopLevelDomain}.{$Domain}.{$PluginName};
>
> import org.bukkit.plugin.java.JavaPlugin;
>
> public final class {$PluginName} extends JavaPlugin {
>
> }
>
> ```
>

### plugin.yml 작성하기

지금까지 프로젝트와 메인 클래스를 만들었습니다. bukkit이 당신의 플러그인을 로드하게 하려면 **plugin.yml** 파일을 생성해야 합니다. 이 파일은 필수 정보들을 담고 있으며 이 파일이 없다면 당신의 플러그인은 작동하지 않을겁니다. 이번에 프로젝트 폴더에서 *src/main/resources*를 마우스 오른쪽 클릭합니다.("src"가 아닙니다). *New > File* 을 선택합니다. 파일 이름을 "**plugin.yml**" 이라고 짓고 ok 를 클릭합니다. 이클립스가 현재 비어있는 **plugin.yml** 파일을 기본 텍스트 편집기에서 열어줄겁니다.*(추가 설명: 텍스트 에디터가 아닌 이클립스에서 plugin.yml을 열고 싶다면 plugin.yml을 이클립스 workspace로 끌어다 놓으세요)*

plugin.yml 에 작성해야할 3가지 기본 정보

name: 당신이 만드는 플러그인의 간단한 이름.
main: 플러그인 메인 클래스의 경로fully qualified name.
version: 당신이 만드는 플러그인의 버전.(마인크래프트 버전이 아닌 자신의 플러그인 버전입니다)
가장 단순한 **plugin.yml** 파일은 이와 같은 모양일겁니다. :

>
> ```
> name: {$PluginName}
> main: {$PackageName}.{$MainClass}
> version: {$VersionNumber}
>
> ```
>

 **Note**: 플러그인의 패키지 이름이 종종 플러그인 이름을 포함할때도 있으니 두번째 줄의 끝부분에서 <pluginname>.<pluginname>과 같은걸 보더라도 너무 이상하게 여기지는 마세요!
 **Note**: 좀 전에 당신의 메인 클래스에 어떤 이름을 붙였는가에 따라서 플러그인 이름과 같을 수도 있고 다를 수도 있습니다. 대소문자를 구분한다는 것을 기억하세요.
***예제를 참고하세요** #Example\_Files\_and\_Templates*

여기까지 하면 Bukkit은 당신의 플러그인을 로드할 수 있고, 이와 함께 로그 메세지가 보일겁니다. 하지만 이 플러그인은 아직 하는건 없습니다!

## onEnable()과 onDisable()

이 메소드들은 플러그인이 활성화, 비활성화될때마다 호출됩니다. 기본으로, 당신의 플러그인은 로드될때 자동으로 활성화됩니다. 따라서 이곳에서 이벤트를 등록하고 디버그용 출력을 제공할 수 있습니다. `onEnable()` 메소드는 플러그인이 활성화될때 호출되고, 이 안에서 당신의 플러그인의 기본적인 설정(데이터 읽기, 인스턴트 만들기)을 해야합니다. `onDisable()`는 플러그인이 비활성화될때 호출되고 여러분이 만든 플러그인과 이에 관련된 상태 정보를 정리/저장하는 코드를 적어야합니다. 그외에 플러그인들은 `onLoad()` 메소드를 override 해서 플러그인이 로드될때 부가적인 로직들을 수행할 수 있습니다.

### onEnable() 와 onDisable() 소개

이전 절에서 생성했던 메인 클래스 안에 `onEnable()` 와 `onDisable()` 메소드를 만듭니다. 아래 코드와 같을겁니다.

>
> ```
> package {$TopLevelDomain}.{$Domain}.{$PluginName};
>
> import org.bukkit.plugin.java.JavaPlugin;
>
> public final class {$PluginName} extends JavaPlugin {
>
>  @Override
>  public void onEnable(){
>  // TODO Insert logic to be performed when the plugin is enabled
>  }
>
>  @Override
>  public void onDisable() {
>  // TODO Insert logic to be performed when the plugin is disabled
>  }
> }
>
> ```
>

이제 메소드가 만들어졌습니다. 하지만 아직 아무것도 하는건 없습니다.

 **Note**: "{$PluginName}이 활성화되었습니다!"와 같은 메세지를 출력할 필요는 없습니다.(bukkit이 자동으로 해줄겁니다.)
### 로그 남기기

플러그인은 콘솔과 서버로그에 메세지를 출력할 수 있습니다. 이 기능은 플러그인의 로거 logger 에서 올바른 메소드를 호출하면 됩니다. 먼저, `getLogger()` 메소드를 호출해서 이 플러그인에 연결된 로거를 얻어내야 합니다. 그리고나서 로그 남기는 일을 시작할 수 있습니다.

`onEnable()` 메소드가 호출될때 로그를 써보겠습니다. `onEnable()` 메소드 안에서 다음의 코드 라인을 삽입하면 됩니다.

>
> ```
> getLogger().info("onEnable 메소드 호출되었음!");
>
> ```
>

`**onDisable()**` 메소드 안에다가도 똑같이 삽입하고 메세지를 바꿔봅니다.

여러분의 메인 클래스는 이제 이와같이 보일겁니다:

>
> ```
> package {$TopLevelDomain}.{$Domain}.{$PluginName};
>
> import org.bukkit.plugin.java.JavaPlugin;
>
> public final class {$PluginName} extends JavaPlugin {
>
>  public void onEnable(){
>  getLogger().info("onEnable 메소드 호출되었음!");
>  }
>
>  public void onDisable(){
>  getLogger().info("onDisable 메소드 호출되었음!");
>  }
> }
>
> ```
>

### 플러그인 리로딩 다루기

플러그인 리로딩 reloading 은 서버를 끄고 재시작할때 일어나기도 하지만, 플러그인이 다른 플러그인들에 의해서 활성화, 비활성되될 수 있고 서버가 실행되는 동안 /reload 명령어 사용을 통해서도 리로딩이 일어난다는 점을 기억하세요. 플러그인이 사용 가능해진 이유가 서버가 방금 시작했기 때문이라고만 가정하는 것은 위험합니다. 왜냐하면 이미 서버에 접속한 온라인 플레이어들이 있을지 모르고 추가적으로 로드된 지도나 단위지형(chunk), 그리고 예상치 못한 다른 것들이 있을 수 있기 때문입니다.

이런 경우가 있습니다.:

* PlayerJoinEvent가 발생했을때 해쉬맵에 플레이어에 대한 정보를 저장하는 플러그인이 있음.
* 플러그인은 모든 플레이어 정보를 해쉬맵을 통해 얻음
* 운영자가 /reload 명령을 사용함(플러그인 리로딩)
* 당신의 플러그인이 비활성화되고 모든 플러그인 데이터가 사라짐
* 온라인상에는 일부 플레이어들이 있고 이들과 함께 당신의 플러그인이 활성화됨.
* 해쉬맵에는 리로드 당시의 플레이어 정보가 들어있지 않음
* 당신의 플러그인은 리로드 당시의 플레이어 정보를 얻어로지 못함

리로드될때 위의 경우가 올바르게 작동하려면 onEnable 되는동안 현재 온라인 상태의 플레이어들을 모두 찾아서 해쉬맵 안에 그 플레이어들의 올바른 정보를 저장할 필요가 있습니다.

>
> ```
> for (Player player : Bukkit.getServer().getOnlinePlayers()) {
>  playerList.put(player.getName(), playerData(player));
> }
>
> ```
>

## 리스너

리스너란 이벤트에 반응해서 메소드가 호출되는 클래스들입니다. 모든 리스너는 `org.bukkit.event.Listener` 를 구현합니다. 리너스를 생성하는 더 자세한 내용은 아래는 참조하세요.

 *여기를 보세요 : Event API Reference*
## 명령어

### onCommand() 메소드

이제 이벤트가 발생할때 무언가 작업을 하는 방법을 알았습니다. 하지만 만약 당신이 명령어(커맨드)를 쳤을때만 무언가 일어나게 할려면 어떻게 해야될까요? 당신은`**onCommand**`를 쓰면 됩니다. 이 코드는 언제든지 사용자들이 "/"글자가 맨앞에 있는 명령어를 치면 실행됩니다. 예를 들어 "/do something"(무언가를 한다)를 치면 `**onCommand**` 가 호출됩니다. 이러한 경우에는 아무런 결과가 프로그래밍 되어있지 않기 때문에 아무 일도 일어나지 않습니다.

버킷에 탑재된 커멘드와 같은 커멘드를 사용하지 마시고, 또한 자신의 커멘드 이름이 독창적이며, 겹칠 소지가 없는지 생각해주십시오. 예를 들어, "give" 커멘드는 이미 많은 플러그인과 서버에서 사용중입니다. 당신이 또 다른 "give" 명령어를 만드려는 경우, 당신의 플러그인은 다른 플러그인과 호환되지 않을 것입니다.

`**onCommand**` 함수는 항상 boolean 값을 반환해야 합니다. (true 또는 false) true를 반환시 아무일도 일어나지 않습니다. 하지만 false를 반환시 plugin.yml파일의 'usage: property'' 가 출력됩니다

`**onCommand**` 사용시 당신은 항상 이 네가지 피라미터를 등록해야 합니다.

* `**CommandSender sender**` - 누가 커멘드를 사용했는가

* `**Command cmd**` - 어떤 커멘드가 실행되었나

* `**String commandLabel**` - 사용된 명령어 이름

* `**String[] args**` - 추가적인 명령어 (예시로, /abc def ghi 의 경우 arg[0]은 def, arg[1]은 def)

#### 커멘드 설정

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
>  if(cmd.getName().equalsIgnoreCase("basic")){ // 만약 플레이어가 /basic 를 쳣을 경우...
>  // 원하는 명령
>  return true;
>  } //성공, 명령어 실행시 true 반환
>  // 만약 실행되지 않으면 false 반환
>  return false;
> }
>
> ```
>

`**onCommand**` 를 편집할 때, 항상 맨 끝에 false를 변환하는 것이 좋습니다. false를 반환할 때에 plugin.yml에서 사용 다이얼로그를 표시합니다. (아래 참조) 아무 것도 작동되지 않거나, 커멘드가 없을 때에는 도움말 메시지를 표시합니다. When returning a value the function will exit so if you return true any code underneath won't be run, unless a return statement is nested in a if statement or similar.

`**.equalsIgnoreCase("basic")**` 는 대소문자를 구분하지 않습니다. 예를 들어, "BAsIc" 이나 "BasiC" 은 똑같이 취급됩니다.

### Plugin.yml 에 명령어 추가

이제 당신은 **plugin.yml** 파일에 당신의 커멘드를 추가해야 합니다. **plugin.yml** 작성법은 아래를 참고하세요:

> ````
>  description: 예시 커멘드입니다.
>  usage: /<커멘드> [플레이어]
>  permission: <플러그인 이름>.basic
>  permission-message: 당신은 <permission> 권한이 없습니다.
>
> ````
>
>

* `**basic**` - 커멘드 이름입니다.

* `**description**` - 커멘드 설명입니다.

* `**usage**` - `**onCommand**` 가 false를 반환할때 보여지는 메시지입니다. 다른 명령과 사용 방법을 명확히 알 수 있게 작성합니다.
* `**permission**` - 몇몇 퍼미션 플러그인에서 사용되며, 권한과 비슷합니다. (의역)
* `**permission-message**` - 플레이어가 이 명령어를 실행했으나 권한(퍼미션)이 없는 경우에 나타나는 메시지입니다.

Note that yml files use 2 spaces for tabs, as the tab character will cause problems.

### 콘솔 커멘드 vs. 플레이어 커멘드

`**CommandSender sender**` 가 보낸 피라미터를 눈치챘을 것이다. `**CommandSender**` 는 두가지 효율적인 보조 클래스를 가지고 있는 버킷 인터페이스입니다. 그것들은: `**Player**` 와 `**ConsoleCommandSender**`.

당신이 플러그인을 제작하고 있다면, it's a very good idea to ensure that commands that *can* be run from the console actually work, and that commands that should only be run as a logged-in player really *are* only run as a logged-in player. Some plugins simply return if the sender is not a player (i.e. someone tried to use the plugin's commands from the console), even when those commands make perfect sense from the console (e.g. changing the weather on the server).

//번역 : 뭔소린지 잘 모르겠네요. 능력자분이 해석해주세요.

그것을 하는 한가지 방법은:

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
>  if (cmd.getName().equalsIgnoreCase("basic")){ // 만일 플레이어가 /basic 을 입력한다면...
>  // 뭔가를 합니다...
>  return true;
>  } else if (cmd.getName().equalsIgnoreCase("basic2")) {
>  if (!(sender instanceof Player)) {
>  sender.sendMessage("This command can only be run by a player.");
>  //이 커멘드는 오직 플레이어에 의해서만 실행될 수 있습니다.
>  } else {
>  Player player = (Player) sender;
>  // 뭔가를 합니다.(?)
>  }
>  return true;
>  }
>  return false;
> }
>
> ```
>

이 예제에서는, **basic** 명령어는 로그인된 플레이어거나 서버 실행자와 관계 없이 누구나 실행 시킬수 있습니다. 그러나, **basic2** 명령어는 무조건 로그인된 플레이어만 실행시킬 수 있죠.

일반적으로, 당신은 콘솔과 플레이어 둘다 실행하게 많은 명령어들을 만들 수 있습니다. 로그인한 플레이어가 사용하게만 만드려고 한다면 위의 예제처럼 `**CommandSender**` 로 정말 사용자인지를 계속하기 전에 체크를 해줍니다. 이렇게 플레이어만 쓸 수 있는 커맨드는 일반적으로 플레이어의 일부 속성에 의존하게 됩니다. 텔레포트 명령은 플레이어가 직접 텔레포트 할 필요가 있고, 아이템을 주는 커멘드는 항목은 아이템을 주는 플레이어가 있어야합니다.

만약 당신이 좀더 심화적으로 무언가를 얻고 싶다면 명령어 인수에 대해서 몇가지 추가 검사를 할수 있습니다. 예를 들어 텔레포트 커멘드는 콘솔에서 사용되어야 합니다*. 만약 그리고 오직* 플레이어의 이름도 함께 언급되었을때 말이죠.

### 별도의 CommandExecutor 클래스 사용

위의 예제는 onCommand () 메소드를 플러그인의 메인 클래스에 넣는 것입니다. 작은 플러그인의 경우에는 괜찮습니다. 그러나 더 광범위한 것을 작성하는 경우 onCommand () 메서드를 자체 클래스에 추가하는 것이 좋습니다. 다행히도 이것은 그리 어렵지 않습니다.
플러그인 패키지 내에 새 클래스를 만듭니다. MyPluginCommandExecutor와 같이 호출하십시오 (물론 MyPlugin을 플러그인의 실제 이름으로 대체하더라도). 이 클래스는 Bukkit CommandExecutor 인터페이스를 구현해야합니다.
플러그인의 onEnable () 메소드에서 새로운 명령 실행기 클래스의 인스턴스를 생성 한 다음 getCommand ( "basic")과 같은 호출을해야합니다 setExecutor (myExecutor); 여기서 "basic"은 명령입니다. 처리하려고하고 myExecutor는 우리가 만든 인스턴스입니다.

### MyPlugin.java (메인 플러그인 클래스) :

>
> ```
> @Override
> public void onEnable() {
>  // ...
>
>  // This will throw a NullPointerException if you don't have the command defined in your plugin.yml file!
>  getCommand("basic").setExecutor(new MyPluginCommandExecutor(this));
>
>  // ...
> }
>
> ```
>

**MyPluginCommandExecutor.java**:

>
> ```
> public class MyPluginCommandExecutor implements CommandExecutor {
>
>  private MyPlugin plugin; // pointer to your main class, unrequired if you don't need methods from the main class
>
>  public MyPluginCommandExecutor(MyPlugin plugin) {
>  this.plugin = plugin;
>  }
>
>  @Override
>  public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
>  // implementation exactly as before...
>  }
> }
>
> ```
>

주 플러그인의 인스턴스를 MyPluginCommandExecutor의 생성자에 전달하는 방법에 유의하십시오. 이렇게하면 필요한 경우 기본 플러그인의 메소드에 쉽게 액세스 할 수 있습니다.

이렇게하면 코드를 더 잘 구성 할 수 있습니다. 기본 onCommand () 메서드가 크고 복잡하면 플러그인의 기본 클래스를 어지럽히 지 않고 하위 메서드로 나눌 수 있습니다.

플러그인에 여러 명령이있는 경우 각 명령에 대해 명령 실행 프로그램을 개별적으로 설정해야합니다.

안전한 onCommand 작성하기

onCommand를 작성할 때 발신자가 Player와 같은 정보를 가정하지 않는 것이 중요합니다. 유의 사항 :

전송 전에 플레이어가 플레이어인지 확인하십시오.

이와 같이 간단한 코드를 사용하면 다음과 같이 가능합니다.

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
>  if (sender instanceof Player) {
>  Player player = (Player) sender;
>  // do something
>  } else {
>  sender.sendMessage("You must be a player!");
>  return false;
>  }
>  // do something
>  return false;
> }
>
> ```
>

#### 보낸 사람이 올바른 양의 인수를 입력했다고 항상 가정하지 마십시오.

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
>  if (args.length > 4) {
>  sender.sendMessage("Too many arguments!");
>  return false;
>  }
>  if (args.length < 2) {
>  sender.sendMessage("Not enough arguments!");
>  return false;
>  }
> }
>
> ```
>

#### When getting another Player by their name, make sure they are online

Sometimes you want to get another player by the name entered by the player. Always make sure the player is online!

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
>  Player target = (Bukkit.getServer().getPlayer(args[0]));
>  if (target == null) {
>  sender.sendMessage(args[0] + " is not online!");
>  return false;
>  }
>  return false;
> }
>
> ```
>

If you need to modify a Player currently not online, the `**OfflinePlayer**` class provides basic manipulation methods.

## Plugin Configuration/Settings

The Bukkit API provides a convenient way for plugins to manage user configurable settings. Additionally it also serves as an easy way to store data.

*Please see: Configuration API Reference*
## Permissions

With the new Bukkit API for permissions, they couldn't be easier. To find out if a player has a particular permission use the following:

>
> ```
> if(player.hasPermission("some.pointless.permission")) {
>  //Do something
> }else{
>  //Do something else
> }
>
> ```
>

You can also find if a permission has been set or not (equivalent to Java's **null**) with the following function:

>
> ```
> boolean isPermissionSet(String name)
>
> ```
>

You may be wondering why there aren't any groups. The answer to that is because they aren't really needed. Previously one of the main uses for groups was to format chat messages. That however can be done just as easily with permissions. Inside your chat plugin's config you would define associations between permissions and prefixes. For example the permission "someChat.prefix.admin" would correspond to the prefix [Admin]. Whenever a player speaks with that permission their name will be prefixed with [Admin].

Another common usage might be to send a message to all users within a group. Again however this can be done with permissions with the following:

>
> ```
> for(Player player: getServer().getOnlinePlayers()) {
>
>  if(player.hasPermission("send.receive.message")) {
>  player.sendMessage("You were sent a message");
>  }
>
> }
>
> ```
>

Finally you may be asking, well how do I set and organise player's permissions if there are no groups? Although the bukkit API doesn't provide groups itself, you must install a permission provider plugin such as permissionsBukkit to manage the groups for you. **This API provides the interface, not the implementation.**

### Configuring your permissions

If you want more control over your permissions, for example default values or children then you should consider adding them to your *plugin.yml*. This is completely optional, however it is advised. Below is an example permissions config that would be appended to the end of your existing *plugin.yml*:

> ````
> permissions:
>  doorman.\*:
>  description: Gives access to all doorman commands
>  children:
>  doorman.kick: true
>  doorman.ban: true
>  doorman.knock: true
>  doorman.denied: false
>  doorman.kick:
>  description: Allows you to kick a user
>  default: op
>  doorman.ban:
>  description: Allows you to ban a user
>  default: op
>  doorman.knock:
>  description: Knocks on the door!
>  default: true
>  doorman.denied:
>  description: Prevents this user from entering the door
>
> ````
>
>

Firstly, each permission your plugin uses is defined as a child node of the *permissions* node. Each permission can then optionally have a description, a default value, and children.

#### Defaults

By default when a permission isn't defined for a player *hasPermission* will return false. Inside your plugin.yml you can change this by setting the default node to be one of four values:

* **true** - The permission will be true by default.
* **false** - The permission will by false by default.
* **op** - If the player is an op then this will be true.
* **not op** - If the player is not an op then this will be true.

#### Children

Before now you will probably be used to the \* permission to automatically assign all sub permissions. This has changed with the bukkit API and you can now define the child permissions. This allows for a lot more flexibility. Below is an example of how you do this:

>
> ```
> permissions:
>  doorman.\*:
>  description: Gives access to all doorman commands
>  children:
>  doorman.kick: true
>  doorman.ban: true
>  doorman.knock: true
>  doorman.denied: false
>
> ```
>

Here the *doorman.\** permission has several child permissions assigned to it. The way child permissions work is when *doorman.\** is set to true, the child permissions are set to their values defined in the *plugin.yml*. If however *doorman.\** was set to false then all child permissions would be inverted.

### Setting your own permissions

If you wish to know about developing your own permissions plugins (Ones that actually set permissions) then check out the tutorial on Developing a permissions plugin.

## Scheduling Tasks and Background Tasks

Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.

Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.

There is a separate Scheduler Programming tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.

## Block Manipulation

The easiest way to create blocks is to get an existing block and modify it. For example, if you want to change the block that is located five blocks above you, you would first have to get your current location, add five to your current y-coordinate, and then change it. For example:

>
> ```
> public void onPlayerMove(PlayerMoveEvent event) {
>  // Get the player's location.
>  Location loc = event.getPlayer().getLocation();
>  // Sets loc to five above where it used to be. Note that this doesn't change the player's position.
>  loc.setY(loc.getY() + 5);
>  World w = loc.getWorld();
>  // Gets the block at the new location.
>  Block b = w.getBlockAt(loc);
>  // Sets the block to type id 1 (stone).
>  b.setTypeId(1);
> }
>
> ```
>

The above code gets the player's location, gets the block five above it, and sets it to stone. Note that once you have a `Block`, there are other things you can do besides set its type. Consult the JavaDocs for more information.

You can use a similar concept to generate buildings and individual blocks programmatically through the use of algorithms. For example, to generate a solid cube, you could use nested `for` loops to loop over an entire cube and fill it in.

>
> ```
> public void generateCube(Location loc, int length){
>  // Set one corner of the cube to the given location.
>  // Uses getBlockN() instead of getN() to avoid casting to an int later.
>  int x1 = loc.getBlockX();
>  int y1 = loc.getBlockY();
>  int z1 = loc.getBlockZ();
>
>  // Figure out the opposite corner of the cube by taking the corner and adding length to all coordinates.
>  int x2 = x1 + length;
>  int y2 = y1 + length;
>  int z2 = z1 + length;
>
>  World world = loc.getWorld();
>
>  // Loop over the cube in the x dimension.
>  for (int xPoint = x1; xPoint <= x2; xPoint++) {
>  // Loop over the cube in the y dimension.
>  for (int yPoint = y1; yPoint <= y2; yPoint++) {
>  // Loop over the cube in the z dimension.
>  for (int zPoint = z1; zPoint <= z2; zPoint++) {
>  // Get the block that we are currently looping over.
>  Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
>  // Set the block to type 57 (Diamond block!)
>  currentBlock.setTypeId(57);
>  }
>  }
>  }
> }
>
> ```
>

This method will construct a 3D cube or cuboid with the given length and starting point. As for deleting blocks simply follow the same method for creating them but set the ID to 0 (air).

## (Player) Inventory Manipulation

This section mostly covers player inventory manipulation, but the same applies to chest inventory manipulation as well if you find out how to get a chest's inventory :P. Here is a simple example of inventory manipulation:

>
> ```
> public void onPlayerJoin(PlayerJoinEvent evt) {
>  Player player = evt.getPlayer(); // The player who joined
>  PlayerInventory inventory = player.getInventory(); // The player's inventory
>  ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // A stack of diamonds
>
>  if (inventory.contains(itemstack)) {
>  inventory.addItem(itemstack); // Adds a stack of diamonds to the player's inventory
>  player.sendMessage("Welcome! You seem to be reeeally rich, so we gave you some more diamonds!");
>  }
> }
>
> ```
>

So inside onPlayerJoin we first make a few variables to make our job easier: player, inventory and itemstack. Inventory is the player's inventory and itemstack is a ItemStack that has 64 diamonds. After that we check if the player's inventory contains a stack of diamonds. If the player has a stack of diamonds, we give him/her another stack with inventory.addItem(itemstack) and send a message. So inventory manipulation isn't actually that hard, if we wanted we could remove the stack of diamonds by simply replacing inventory.addItem(itemstack) with inventory.remove(itemstack) and change the message a little bit. Hopefully this helped!

## Item Manipulation

When dealing with items in the code, you use the ItemStack class for looking up and setting all information on that stack.

### Enchantments

To enchant an item you must first know the Item Code and the Effect ID. Enchantments themselves cannot be instantiated (new Enchantment() won't work) because they're abstract, so you must use an EnchantmentWrapper. If you want to enchant items that can't be enchanted inside normal SMP, use addUnsafeEnchantment() instead of addEnchantment()

```
int itemCode = 280; //use the item code you want here
int effectId = 20; //use the enchantment code you want here
int enchantmentLevel = 100;

ItemStack myItem = new ItemStack(itemCode); //new item of item code
Enchantment myEnchantment = new EnchantmentWrapper(effectId); //new enchantment of effect id
myItem.addEnchantment(myEnchantment, enchantmentLevel); //enchant the item

```

## Maps, and Sets, and Lists, Oh My!

Besides the Map/HashMap classes, Java offers many other data structures. They offer these different classes because there are times when a Map is not the most appropriate. Here's a separate page for discussing Java data structure classes in more detail.

### HashMaps and How to Use Them

When making a plugin you will get to a point where just using single variables to state an event has happened or a condition has been met will be insufficient, due to more than one player performing that action/event.

This was the problem I had with one of my old plugins, Zones, now improved and re-named to Regions. I was getting most of these errors because I didn't consider how the plugin would behave on an actual server with more than one on at any given time. I was using a single boolean variable to check whether players were in the region or not and obviously this wouldn't work as the values for each individual player need to be separate. So if one player was in a region and one was out the variable would constantly be changing which could/would/did cause numerous errors.

A HashMap is an excellent way of doing this. A HashMap is a way of mapping/assigning a value to a key. You could set up the HashMap so that the key is a player and the value could be anything you want, however the useful things with HashMaps is that one key can only contain one value and there can be no duplicate keys. So say for example I put "adam" as the key and assigned a value of "a" to it. That would work as intended, but then say afterwards I wanted to assign the value of "b" to key "adam" I would be able to and would get no errors but the value of "a" assigned to key "adam" in the HashMap would be overwritten because HashMaps cannot contain duplicate values.

#### Defining a HashMap

>
> ```
> public Map<Key, DataType> HashMapName = new HashMap<Key, Datatype>(); //Example syntax
>
> //Example Declaration
>
> public Map<Player, Boolean> pluginEnabled = new HashMap<Player, Boolean>();
> public Map<Player, Boolean> isGodMode = new HashMap<Player, Boolean>();
>
> ```
>

Keep that code in mind because we will be using it for the rest of the tutorial on HashMaps. So, for example lets create a simple function which will toggle whether the plugin has been enabled or not. Firstly, inside your on command function which I explained earlier you will need to create a function to send the player name to the function and adjust the players state accordingly.

So inside on command you'll need this, the function name can be different but for the sake of simplicity it's best if you keep it the same.

>
> ```
> Player player = (Player) sender;
> togglePluginState(player);
>
> ```
>

This code above will cast the value of sender to player and pass that argument to the function togglePluginState(). But now we need to create our togglePluginState() function.

>
> ```
> public void togglePluginState(Player player){
>
>  if(pluginEnabled.containsKey(player)){
>  if(pluginEnabled.get(player)){
>  pluginEnabled.put(player, false);
>  player.sendMessage("Plugin disabled");
>  } else {
>  pluginEnabled.put(player, true);
>  player.sendMessage("Plugin enabled");
>  }
>  } else {
>  pluginEnabled.put(player, true); //If you want plugin enabled by default change this value to false.
>  player.sendMessage("Plugin enabled");
>  }
>
> }
>
> ```
>

Now, what this code is doing is checking if the HashMap first contains the key player, so if it has been put into the HashMap, if it is then we check the value of the HashMap key by get(player); if this is true then set value to false and send the player a message, else if the value is false then do the opposite, set the value to true and send a message again. But if the HashMap does not contain the key player then we can assume that this is their first run/use so we change the default value and add the player to the HashMap.

#### More Ideas for HashMaps

A HashMap (or really any kind of Map in Java) is an association. It allows quick and efficient lookup of some sort of **value**, given a unique **key**. Anywhere this happens in your code, a Map may be your solution.

Here are a few other ideas which are ideally suited to using Maps. As you will see, it doesn't have to be data that you store per player, but can be any kind of data that needs to be "translated" from one form to another.

##### Data Value Lookups

>
> ```
> public Map<String, Integer> wool\_colors = new HashMap<String, Integer>();
>
> // Run this on plugin startup (ideally reading from a file instead of copied out row by row):
> wool\_colors.put("orange", 1);
> wool\_colors.put("magenta", 2);
> wool\_colors.put("light blue", 3);
>  ..
> wool\_colors.put("black", 15);
>
> // Run this in response to user commands - turn "green" into 13
> int datavalue = 0;
> if (wool\_colors.containsKey(argument)) {
>  datavalue = wool\_colors.get(argument);
> } else {
>  try { datavalue = Integer.parseInt(argument); }
>  catch (Exception e) { ; }
> }
>
> ```
>

#### Saving/Loading a HashMap

Once you know how to work with HashMaps, you probably want to know how to save and load the HashMap data. Saving and loading HashMap data is appropriate if

* you don't want an administrator to edit the data manually

* you need to save data in binary format (too complex to organize for YAML)

* you want to avoid parsing block names and/or other objects from freeform text

This is very simple way how to save any HashMap. You can replace HashMap<String, Integer> with any type of HashMap you want. Let's continue using the "pluginEnabled" HashMap defined from the previous tutorial. This code saves the given HashMap to the file with given path.

>
> ```
> public void save(HashMap<String, Integer> map, String path)
> {
>  try
>  {
>  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
>  oos.writeObject(map);
>  oos.flush();
>  oos.close();
>  //Handle I/O exceptions
>  }
>  catch(Exception e)
>  {
>  e.printStackTrace();
>  }
> }
>
> // ...
>
> save(pluginEnabled, getDataFolder() + File.separator + "example.bin");
>
> ```
>

You can see it's really easy. Loading works very very similar but we use ObjectInputStream instead of ObjectOutputStream ,FileInputStream instead of FileOutputStream,readObject() instead of writeObject() and we return the HashMap.

>
> ```
> public HashMap<String, Integer> load(String path)
> {
>  try
>  {
>  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
>  Object result = ois.readObject();
>  //you can feel free to cast result to HashMap<String, Integer> if you know there's that HashMap in the file
>  return (HashMap<String, Integer>)result;
>  }
>  catch(Exception e)
>  {
>  e.printStackTrace();
>  }
> }
>
> // ...
>
> String path = getDataFolder() + File.separator + "example.bin";
> File file = new File(path);
>
> if(file.exists()) // check if file exists before loading to avoid errors!
>  pluginEnabled = load(path);
>
> ```
>

You can use this "API" for saving/loading HashMaps, ArrayLists, Blocks, Players... and all Objects you know ;) . Please credit Tomsik68(the author of this) if you use this in your plugin/other project.

>
> ```
> /\*\* SLAPI = Saving/Loading API
>  \* API for Saving and Loading Objects.
>  \* You can use this API in your projects, but please credit the original author of it.
>  \* @author Tomsik68<tomsik68@gmail.com>
>  \*/
> public class SLAPI
> {
>  public static <T extends Object> void save(T obj,String path) throws Exception
>  {
>  ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
>  oos.writeObject(obj);
>  oos.flush();
>  oos.close();
>  }
>  public static <T extends Object> T load(String path) throws Exception
>  {
>  ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
>  T result = (T)ois.readObject();
>  ois.close();
>  return result;
>  }
> }
>
> ```
>

Example implementation of this API: **I'm skipping some part of code in this source**

>
> ```
> public class Example extends JavaPlugin {
>  private ArrayList<Object> list = new ArrayList<Object>();
>  public void onEnable()
>  {
>  try{
>  list = SLAPI.load("example.bin");
>  }catch(Exception e){
>  //handle the exception
>  e.printStackTrace();
>  }
>  }
>  public void onDisable()
>  {
>  try{
>  SLAPI.save(list,"example.bin");
>  }catch(Exception e){
>  e.printStackTrace();
>  }
>  }
> }
>
> ```
>

A minor note about this SLAPI and Java's ObjectOutputStream class. This will work un-modified if you are saving almost all well-known Java types like Integer, String, HashMap. This will work un-modified for some Bukkit types as well. If you're writing your own data object classes, and you may want to save their state using this technique, you should read up on Java's Serializable or Externalizable interface. The only difference between Externalizable and Serializable is, that Serializable automatically takes all of class's fields and tries to serialize them, while Externalizable allows you to define method for reading and writing the Object. It's easy to add to your code, and it will make your data persistent with very little work on your part. No more parsing!

## Metadata

Bukkit is trying to make plugin development as easy as possible, so HashMaps with key of type Player, Entity, World or even a Block were replaced by Metadata. Metadata is some kind of alternative to HashMap. It allows you to add custom "fields" to Players, Entities, Worlds and Blocks. These things are all members of Metadatable class(check [1])It works very simply. Everything that is Metadatable holds its own HashMap of Metadata which you have access to. That means, for example, if you're creating an economy plugin, you would need a HashMap of Player and Float or Double. With Metadata, you don't have to! You just attach to player new metadata value, and that's it!

### Why to use Metadata

Metadata is all handled by Bukkit, which makes it a very good alternative to HashMaps.

Metadata can be used to share information between plugins.

### Why not use Metadata

Slightly more difficult to get the value.

It is not saved on shutdown.

### Getting & Setting Metadata

```
public void setMetadata(Player player, String key, Object value, Plugin plugin){
 player.setMetadata(key,new FixedMetadataValue(plugin,value));
}
public Object getMetadata(Player player, String key, Plugin plugin){
 List<MetadataValue> values = player.getMetadata(key);
 for(MetadataValue value : values){
 if(value.getOwningPlugin().getDescription().getName().equals(plugin.getDescription().getName())){
 return value.value();
 }
 }
}

```

Note: If you're manipulating with numbers, booleans or strings, use convenient method to get the result. For example, you can use asInt, asString or asBoolean instead of value to find out the value.

## Databases

Sometimes flat files aren't enough for what your looking to do, this is where databases come in. The most common database engines available on Linux/Mac/Windows machines typically run on some flavor of SQL (Structured Query Language).

Software offering SQL allow you to create databases with columns and header to identify to contents of each cell. Think of it as a spreadsheet on steroids, where every column you set up in your database can enforce rules to ensure integrity. Apart from being more organised than a simple custom data file, SQL provides faster access and better searching than flat files.

The SQL standard helps applications like Bukkit implement database storage for their data in a consistent way. Unfortunately, there's more than one SQL-ready database engine, and each has minor differences in how to configure and use it. Which one you choose may depend on your particular needs. (Some plugins even offer configurable options to connect to multiple database engines!)

### SQLite

Alta189 has written a fantastic SQLite tutorial which I suggest you watch if you're interested in using SQL in your plugins, included with the tutorials is a handy library you can download and import to make using SQL easier. Once you have watched these video tutorials I would suggest you go and learn some SQL syntax, it's very straightforward and shouldn't take you long to pick up. SQL Tutorials @W3Schools and @1Keydata.

SQLite is great for very simple databases, because there's no server concerns to set up. Just make a few calls to create a new database and table. It's easy to back up: just copy the whole database file in one go. SQLite is a little bit weaker at data integrity, flexibility in data types, and it may not be something you would want to trust for huge databases of millions of rows. But for a new plugin in development, it's often easiest and fastest to get the SQL basics squared away with SQLite, even if you "graduate" to a more server-class database engine later.

### MySQL

Another popular SQL database engine is called MySQL. It is closer to server-grade than SQLite, where many popular companies or websites depend on it for millions of webpage hits every day. With that security comes a little bit steeper learning-curve, because MySQL has more tunable parameters and capabilities.

The coding for plugins accessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle, with only small differences in syntax here or there. But the administration has room to grow. You may want to set up accounts and privileges inside your MySQL setup. You may want to set up SQL scripts that organize your backups and rollback to previous states.

## Deploying your Plugin

Once you have written your plugin, how do you get it from a collection of source files into a working jar file that can be installed on a server? First, set up a CraftBukkit server on your local machine. To do this, visit the wiki page on Setting up a server. Next you have to export your plugin to a .jar so that you can run it on your new server. To do this in Eclipse, click File > Export. In the window that pops up, under "Java", select "JAR file", and click next. You will see a window that looks like this:

On the left, make sure that your src folder is selected. On the right, the two files beginning with a decimal point are to do with Eclipse and are not important to your plugin, so deselect them. It is vital that plugin.yml is selected in order for your plugin to work. Export your JAR file to any destination you like, but make sure you know where it is.

The jar file you have exported should now be a working plugin! Assuming of course that there are no errors in your code or your plugin.yml file. You can now drop the jar file you have exported into your bukkit server's "plugins" folder, reload or relaunch the server, and test away! In order to connect to a server running locally on your computer, simply put "localhost" as the IP address of the server in Minecraft multiplayer. If you run into errors that you can't solve for yourself, try visiting the plugin development forum, asking in the bukkitdev IRC channel, or re-reading this wiki. Once you have a useful working plugin, consider submitting your project to dev.bukkit for consumption by the Bukkit community. From the wizard above, you can see that the JAR file will be by default a compressed archive (JARs are based on the ZIP archive format). As such, it does not make sense to put your JAR into a ZIP archive when uploading to BukkitDev and will only increase the file size. Further, config files can be placed within the JAR and copied into the plugin's data folder if the configuration file does not exist. There is usually no good reason for packaging JAR files into another archive.

## Tips and Tricks

### 플레이어에게 불을 붙이자

The CraftBukkit API is capable of a lot of cool stuff. Here are some code snippets for some nice effects!

The following code allows a player to set another player on fire. Running a command like **/ignite Notch** would cause Notch to be set on fire!

```
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 // Uses equalsIgnoreCase() over equals() to accept "ignite" and "IgNiTe."
 if (cmd.getName().equalsIgnoreCase("ignite")) {
 // Make sure that the player specified exactly one argument (the name of the player to ignite).
 if (args.length != 1) {
 // When onCommand() returns false, the help message associated with that command is displayed.
 return false;
 }

 // Make sure the sender is a player.
 if (!(sender instanceof Player)) {
 sender.sendMessage("Only players can set other players on fire.");
 sender.sendMessage("This is an arbitrary requirement for demonstration purposes only.");
 return true;
 }

 // Get the player who should be set on fire. Remember that indecies start with 0, not 1.
 Player target = Bukkit.getServer().getPlayer(args[0]);

 // Make sure the player is online.
 if (target == null) {
 sender.sendMessage(args[0] + " is not currently online.");
 return true;
 }

 // Sets the player on fire for 1,000 ticks (there are ~20 ticks in second, so 50 seconds total).
 target.setFireTicks(1000);
 return true;
 }
 return false;
}

```

### 플레이어를 죽이자

To keep with the theme, here's a way to kill the player.

Use this for your onCommand method:

>
> ```
> public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
>  if(cmd.getName().equalsIgnoreCase("KillPlayer")){
>  Player target = sender.getServer().getPlayer(args[0]);
>  // Make sure the player is online.
>  if (target == null) {
>  sender.sendMessage(args[0] + " is not currently online.");
>  return true;
>  }
>  target.setHealth(0);
>  }
>  return false;
> }
>
> ```
>

Here is an extension to that, that will kill the player with an explosion:

>
> ```
> float explosionPower = 4F; //This is the explosion power - TNT explosions are 4F by default
> Player target = sender.getWorld().getPlayer(args[0]);
> target.getWorld().createExplosion(target.getLocation(), explosionPower);
> target.setHealth(0);
>
> ```
>

### 가짜 폭발을 생성하자

This code produces the TNT/Creeper Visual and Audio effects. However, no explosion damage is dealt to surrounding entities or blocks. This is useful for nerfing explosions while still keeping the aesthetics of them.

>
> ```
> @EventHandler
> public void onExplosionPrime(ExplosionPrimeEvent event) {
>  Entity entity = event.getEntity();
>
>  // If the event is about primed TNT (TNT that is about to explode), then do something
>  if (entity instanceof TNTPrimed) {
>  entity.getWorld().createExplosion(entity.getLocation(), 0);
>  }
> }
>
> ```
>

### 다른 플레이어로부터 자신을 숨기자

This will hide the player who used this command from a specified player. Everyone else will be able to see the player.

```
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 if(cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) {
 if (!(sender instanceof Player)) {
 sender.sendMessage("Only players can use this command!");
 return true;
 }
 // After checking to make sure that the sender is a Player, we can safely case it to one.
 Player s = (Player) sender;

 // Gets the player who shouldn't see the sender.
 Player target = Bukkit.getServer().getPlayer(args[0]);
 if (target == null) {
 sender.sendMessage("Player " + args[0] + " is not online.");
 return true;
 }
 // Hides a given Player (s) from someone (target).
 target.hidePlayer(s);
 return true;
 }
 return false;
}

```

### 플레이어가 보는곳에 번개를 내리자

The code below allows any player with a fishing rod to create a lightning strike by clicking (and aiming somewhere). It's a simple and funny trick.

```
@EventHandler
public void onPlayerInteractBlock(PlayerInteractEvent event) {
 Player player = event.getPlayer();
 if (player.getItemInHand().getType() == Material.FISHING\_ROD) {
 // Creates a bolt of lightning at a given location. In this case, that location is where the player is looking.
 // Can only create lightning up to 200 blocks away.
 player.getWorld().strikeLightning(player.getTargetBlock(null, 200).getLocation());
 }
}

```

## Request Section

http://forums.bukkit.org/forums/plugin-requests.13/

## Example Files and Templates

* Bukkit/SamplePlugin on Github

* Example.Java

* ExamplePlayerListener.Java

* ExampleBlockListener.Java

* ExampleEntityListener.Java

If you have any more questions on this matter, don't hesitate to visit the BukkitDev IRC channel and ask!