이번에도 Listener 와 조금 더 친해지기 위해 플러그인 개발을 실습해보도록 하겠습니다.
2편 에서 서버와의 통신에 대해 이야기할 때 플레이어가 검으로 좀비를 공격했을 때 체력을 알 수 있는 플러그인이 있으면 좋겠다 생각했는데요.
이번에는 Damage Display 플러그인을 개발해보도록 하겠습니다.
플러그인을 만들기 전에 우리는 어떤 플러그인을 만들 것인지 기획을 먼저 하는 과정을 거치기로 했는데요.
우리가 만들고 싶은 플러그인은 플레이어가 동물이나 몬스터를 공격했을 때 입힌 데미지를 플레이어에게 알려주는 플러그인 입니다.
플레이어가 어떠한 것을 공격을 했을 때
입힌 데미지를
플레이어에게 알려준다.
이렇게 세 개의 기능으로 분리할 수 있는데요.
플레이어가 공격을 한다는 것은 어떻게 알 수 있을까요 ?
플러그인은 이벤트라는 신호를 서버로부터 전달받는다고 했습니다.
구글에 spigot player attack event 라고 검색을 해보도록 하겠습니다.
Player Attack Event | Bukkit Forums
Player Attack Event | Bukkit Forums
bukkit.org
가장 최상단에 Player Attack Event 에 대해 이야기해주는 것 같습니다.
아주 간결한 답변으로 EntityDamageByEntityEvent 라는 것을 활용하면 된다고 알려주는 것 같은데요.
spgiot 에 EntityDamageByEntityEvent 를 검색해보도록 하겠습니다.
EntityDamageByEntityEvent (Spigot-API 1.21.1-R0.1-SNAPSHOT API) (spigotmc.org)
EntityDamageByEntityEvent (Spigot-API 1.21.1-R0.1-SNAPSHOT API)
All Implemented Interfaces: Cancellable Called when an entity is damaged by an entity Nested Class Summary Field Summary Constructor Summary Constructors Deprecated, for removal: This API element is subject to removal in a future version. Deprecated, for r
hub.spigotmc.org
EntityDamageByEntityEvent 는 Class 이고 org.bukkit.event.entity 라는 패키지 안에 존재한다는 것 같습니다.
내용을 살펴보면 entity 가 entity 에 의해 데미지를 받으면 호출되는 이벤트라는 것 같습니다.
존재자 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)
존재자 - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전.
ko.wikipedia.org
이 entity 는 실존하는 무언가 라는 의미라고 하는데요.
만약 플레이어가 좀비를 공격한다고 하면 플레이어와 좀비는 실존하는 것이기 때문에 플레이어와 좀비는 entity 라고 생각할 수 있습니다.
EntityDamageByEntityEvent Class 가 가지고 있는 Method Summary 를 보면
getDamage 와 getEntity 라는 것을 통해 입힌 데미지와 플레이어가 공격한 엔티티에 대한 정보를 얻을 수 있을 것 같습니다.
하지만, getEntity 의 설명을 읽어보면 이벤트를 유발한 엔티티를 돌려준다고 하고 있기에
여기서 말하고있는 getEntity 는 플레이어의 정보를 알려주는 것 같습니다.
Method Summary 를 다시 읽어보면 getDamager() 라는 메서드를 통해 공격당한 엔티티의 정보를 알 수 있는 것 같습니다.
그렇다면 우리는 필요한 정보들이 모두 모인 것 같습니다.
우리가 만들고 싶은 플러그인은
플레이어가 어떠한 것을 공격을 했을 때
입힌 데미지를
플레이어에게 알려준다.
입니다.
플레이어가 공격한 대상의 정보는 getDamager() 를 통해 얻을 수 있고
입힌 데미지는 getDamage() 를 통해 알 수 있고
플레이어의 정보는 getEntity() 메서드를 통해서 얻을 수 있을 것 같습니다.
그러면 이제 플러그인을 개발해보도록 하겠습니다.
이번에는 조금 다른 방식으로 플러그인을 개발해보도록 할텐데요.
기존 플러그인 개발은
1. maven 프로젝트 제작
2.pom.xml 파일 구성
3. 플러그인 개발
4. plugin.yml 파일 제작
5. 플러그인 적용
의 순서로 진행 했습니다. 이번에는 2번 pom.xml 파일 구성을 생략할텐데요.
pom.xml 이 하는 일 중 하나는 Bukkit 라이브러리를 가져오는 역할을 한다고 이야기 했었습니다.
이 Bukkit 라이브러리는 BuildTools 를 통해 커스텀 서버를 구성했으면 이미 가지고 있는 파일이기에
Bukkit 라이브러리를 가져오기 위해 별도의 pom.xml 을 구성하는 것을 하지 않고 버전 관리의 목적으로만 사용해보도록 하겠습니다.
우선 Maven 프로젝트를 구성하는 것은 동일합니다.
저는 Group Id 는 com.tistory.beingb 로 구성하고
플러그인 이름이자 Artifact Id 는 DamageDisplay 로 짓도록 하겠습니다.
그러면 위와 같은 프로젝트의 구성이 되는데요.
원래 이 다음 과정으로는 pom.xml 파일을 수정해 Bukkit 라이브러리를 가져왔었습니다.
그 다음은 프로젝트에서 우클릭 > Build Path > Configure Build Path 를 클릭합니다.
그러면 Properties for DamageDisplay 창이 뜨고 Java Build Path 를 지정할 수 있는 창이 등장합니다.
우측에 Add External JARs 를 클릭합니다.
SPIGOT 서버가 있는 server 폴더 내에
Spigot > Spigot-API > target > spigot-api-1.21.1-R0.1-SNAPSHOT.jar 파일이 존재합니다.
이 파일을 Build path 의 Library 에 추가합니다.
그러면 위와 같이 spigot-api 가 Libraries 탭에 추가가 된 것을 확인할 수 있습니다.
위 작업이 pom.xml 파일에 dependency 를 설정하는 것과 같은 역할을 하는 것입니다.
다만 pom.xml 은 외부의 인터넷에서 Bukkit API 를 가져오는 것이고
Build Path 에 추가하는것은 내가 가지고 있던 Bukkit API 를 지정해주는 것입니다.
이렇게 프로젝트의 구성을 마치고 패키지와 메인 클래스를 제작해보도록 하겠습니다.
패키지의 이름은 com.tistory.beingb 로 구성하고
메인 클래스의 이름은 DamageDisplay 로 하도록 하겠습니다.
해당 창은 클래스의 여러 정보들을 결정하는 곳인데요.
메인 클래스가 되기 위해서는 extend JavaPlugin 을 해야 하고
이벤트 리스너가 되기 위해서는 implements Listener 를 해야 한다고 했습니다.
Listener 은 interface 라는 것을 리서칭을 통해 알 수 있는데요.
중간에 있는 interfaces 의 Add 버튼을 통해 Listener 를 미리 추가해줄 수 있습니다.
이렇게 Listener 를 미리 구성할 수 있습니다.
제작한 클래스는 메인 클래스이기에, extend JavaPlugin 도 추가해줍니다.
플러그인을 제작할 준비를 마쳤으니 잊지 않게 plugin.yml 도 구성해줍니다.
우선 EntityDamageByEntityEvent 를 활용하기 위해 onDamage 라는 메서드를 구성해주도록 하겠습니다.
@EventHandler 라는 annotation 을 추가해주고
onDamage 라는 메서드를 제작하고
INPUT 으로 EntityDamageByEntityEvent 를 추가해줍니다.
package com.tistory.beingb;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class DamageDisplay extends JavaPlugin implements Listener {
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
}
}
이렇게 이벤트 핸들러는 구성했습니다.
그리고 onEnable 에 이벤트 핸들러를 등록해줘야 하는데요.
이번엔 onEnable 과 onDisable 를 Eclipse IDE 를 통해 간단하게 제작해보도록 하겠습니다.
상단의 Source 탭을 보면 Override/Implement Methods... 라는 버튼이 존재합니다.
해당 버튼을 누르면 메인 클래스가 사용할 수 있는 메서드 들이 등장하는데요.
onDisable 과 onEnable 를 추가해보도록 하겠습니다.
package com.tistory.beingb;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.plugin.java.JavaPlugin;
public class DamageDisplay extends JavaPlugin implements Listener {
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
}
@Override
public void onDisable() {
// TODO Auto-generated method stub
super.onDisable();
}
@Override
public void onEnable() {
// TODO Auto-generated method stub
super.onEnable();
}
}
그러면 위와 같은 코드가 구성되게 됩니다.
6장과 동일하게 플러그인 매니저에 이벤트를 등록해줍니다.
@Override
public void onEnable() {
// TODO Auto-generated method stub
super.onEnable();
getServer().getPluginManager().registerEvents(this, this);
}
플러그인을 만들 준비는 모두 끝났습니다.
그러면 우리가 만들 플러그인의 기획을 다시 한 번 살펴보면
플레이어가 어떠한 것을 공격을 했을 때
입힌 데미지를
플레이어에게 알려준다.
입니다.
우선 플레이어에게 입힌 데미지를 메세지로 전달해보도록 하겠습니다.
플레이어의 정보는 getEntity 로 가져올 수 있고 6 장에서 활용했던 sendMessage 와 getDamage 를 활용하면 좋을 것 같습니다.
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
event.getEntity().sendMessage(event.getDamage());
}
위와 같이 코드를 구성하니
The method sendMessage(String) in the type CommandSender is not applicable for the arguments (double)
이라는 오류가 뜹니다.
arguments (double) 을 sendMessage(String) 에 적용할 수 없다는 오류인 것 같습니다.
이 double 이라는 것이 String 에 왜 적용이 안되는지 어떻게 적용을 시키면 되는지 모르기에
구글에 spigot double string 이라고 검색해보도록 하겠습니다.
Solved - Double cannot be converted to String | SpigotMC - High Performance Minecraft
SpigotMC 에 Double cannot be converted to String 라는 글이 보입니다.
답변을 보아하니 Double.toString 이라는 것으로 Double 이라는 것을 String 이라는 것으로 변경시킬 수 있다는 것 같습니다.
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
event.getEntity().sendMessage(Double.toString(event.getDamage()));
}
이런식으로 event.getDamage() 를 Double.toString() 으로 감싸주면 아까까지 등장하던 에러가 해결된 것을 볼 수 있습니다.
이렇게 maven 을 통해 플러그인을 제작해 적용시켜보도록 하겠습니다.
2.5 라는 값이 채팅창에 올라오는 것이 보입니다.
우리는 여기서 Double 이라는 것이 소수점 이라는 것을 알 수 있고 그것을 문자의 형태로 변경했다는 것을 알 수 있습니다.
그런데 뭔가 이상합니다.
플레이어가 공격할 때 2.5 라는 값이 출력되는 게 아니라
플레이어가 좀비에게 공격당할 때 값이 출력되는 것으로 보입니다.
그러면 플레이어가 공격할 때는 해당 이벤트가 발동하지 않는걸까요 ?
공격할 때 이벤트가 발생하는지 서버의 로깅을 추가하는 작업을 해보도록 하겠습니다.
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
getLogger().info("CALL EntityDamageByEntityEvent");
getLogger().info(Double.toString(event.getDamage()));
event.getEntity().sendMessage(Double.toString(event.getDamage()));
}
이번에는 좀비에게 맞지 않는 위치에서 좀비를 공격해보도록 하겠습니다.
역시나 공격할 때는 아무것도 출력되지 않습니다.
그런데 서버의 로그에는 EntityDamageByEntityEvent 가 호출되고
4.0 이라는 데미지도 정상적으로 노출되고 있습니다.
그러면 무엇이 잘못된걸까요 ?
바로 플러그인에 대한 저의 해석이 잘못됐다는 것을 알 수 있었습니다.
좀비가 플레이어를 공격할 때
데미지가 출력되고
플레이어에게 2.5 라는 값이 출력 됐습니다.
그러면 해당 상황에서
공격을 한 좀비는 Damager 이 되고
공격을 받은 플레이어는 Entity 가 되고
출력된 Damage 는 좀비가 플레이어를 공격했을 때의 데미지가 보내진 것을 알 수 있었습니다.
그러면 이번 플러그인은 유저가 좀비를 공격했을 때 데미지를 출력하고 싶은 것입니다.
유저가 좀비를 공격한다면
공격을 한 유저는 Damager 이 되고
공격을 받은 좀비는 Entity 가 되고
출력된 Damage 는 플레이어가 좀비를 공격했을 때의 데미지가 된다는 의미입니다.
그러면 sendMessage 는 Damager 에서 작동해야 하는 메서드라는 것을 알 수 있습니다.
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
getLogger().info("CALL EntityDamageByEntityEvent");
getLogger().info(Double.toString(event.getDamage()));
event.getDamager().sendMessage(Double.toString(event.getDamage()));
}
sendMessage 가 사용되는 주체를 Damager 로 변경합니다.
성공적으로 유저가 좀비를 공격했을 때 4.0 이라는 값이 출력되는 것을 볼 수 있습니다.
데미지만 노출되니 뭔가 보기가 심심합니다.
<공격을 한 대상의 이름> 이 <공격을 받은 대상의 이름> 에게 <데미지> 의 데미지를 입혔습니다.
<공격을 받은 대상의 이름> 의 남은 체력은 <공격받은 대상의 체력> 입니다.
로 변경되면 좋을 것 같습니다.
getDamager 와 getEntity 는 Entity 라는 것을 돌려준다고 위에서 이야기 했습니다.
그러면 이 Entity 는 어떠한 정보들을 가지고 있는걸까요 ?
Entity (Spigot-API 1.21.1-R0.1-SNAPSHOT API) (spigotmc.org)
Entity (Spigot-API 1.21.1-R0.1-SNAPSHOT API)
All Superinterfaces: CommandSender, Metadatable, Nameable, Permissible, PersistentDataHolder, ServerOperator All Known Subinterfaces: AbstractArrow, AbstractHorse, AbstractSkeleton, AbstractVillager, AbstractWindCharge, Ageable, Allay, Ambient, Animals, Ar
hub.spigotmc.org
Entity 는 Interface 라는 것이고
Method Summary 를 보면 엄청나게 많은 메서드들을 포함하고 있는 것을 확인 할 수 있었습니다.
우리에게 필요한 것은
Entity 의 이름
데미지
Entity 의 체력
입니다.
우리는 플레이어가 접속 했을 때 이름을 출력하는 실습을 통해
이 Entity 에는 getName() 이라는 메서드가 있다는 것을 알고 있습니다.
데미지는 event.getDamage() 를 통해 가져올 수 있을 것 같고
Entity 의 체력은 어떻게 가져올 수 있는걸까요 ?
정보가 없을 때는 검색을 통해 찾아봐야 합니다.
spigot get Entity HP 라는 내용으로 검색해보도록 하겠습니다.
Solved - Check an entity's health [EntityDamageByEntityEvent] | Bukkit Forums
Solved - Check an entity's health [EntityDamageByEntityEvent] | Bukkit Forums
bukkit.org
해당 답변을 보면 getHealth() 라는 메서드를 통해 체력을 가져올 수 있는 것 같습니다.
Damageable (Spigot-API 1.21.1-R0.1-SNAPSHOT API) (spigotmc.org)
Damageable (Spigot-API 1.21.1-R0.1-SNAPSHOT API)
All Superinterfaces: CommandSender, Entity, Metadatable, Nameable, Permissible, PersistentDataHolder, ServerOperator All Known Subinterfaces: AbstractHorse, AbstractSkeleton, AbstractVillager, Ageable, Allay, Ambient, Animals, Armadillo, ArmorStand, Axolot
hub.spigotmc.org
getHealth 라는 것을 검색해보면 엔티티의 체력을 가져와준다고 합니다.
그리고 0이 되면 죽는다고 하네요.
그러면 이러한 정보들을 통해
<공격을 한 대상의 이름> 이 <공격을 받은 대상의 이름> 에게 <데미지> 의 데미지를 입혔습니다.
<공격을 받은 대상의 이름> 의 남은 체력은 <공격받은 대상의 체력> 입니다.
를 코드로 옮겨보도록 하겠습니다.
@EventHandler
public void onDamage(EntityDamageByEntityEvent event) {
getLogger().info("CALL EntityDamageByEntityEvent");
getLogger().info(Double.toString(event.getDamage()));
event.getDamager().sendMessage(
event.getDamager().getName() +
" 이 " +
event.getEntity().getName() +
" 에게 " +
Double.toString(event.getDamage()) +
" 의 데미지를 입혔습니다."
);
event.getDamager().sendMessage(
event.getEntity().getName() +
" 의 남은 체력은 " +
event.getEntity().getHealth() +
" 입니다."
);
}
코드를 작성했는데, getHealth() 부분에 에러가 뜨고 있습니다.
The method getHealth() is undefined for the type Entity
getHealth() 라는 메서드가 Entity 타입에 정의되어 있지 않다는 에러가 뜨는 것 같습니다.
spigot get entity health 로 다시 검색해보도록 하겠습니다.
가장 최상단에 HP 를 얻기 위한 방법을 질문하는 글이 있는 것 같습니다.
해당 답변을 보아하니 괄호로 감싼 다음 앞의 괄호에 LivingEntity 라는 것을 넣어주면 해결할 수 있을 것 같습니다.
왜 Entity 에 getHealth() 라는 메서드가 없는가 하니 ItemFrame 나 Leash 도 Entity 라는 것에 속하는 것 같은데
이러한 것들은 체력이 없으니, getHealth 를 모든 상황에서 호출할 수가 없다는 것 같습니다.
if(event.getEntity() instanceof LivingEntity) {
event.getDamager().sendMessage(
event.getEntity().getName() +
" 의 남은 체력은 " +
((LivingEntity)event.getEntity()).getHealth() +
" 입니다."
);
}
아래 getHealth() 메서드를 호출하는 부분의 entity 에 LivingEntity 라는 것으로 감싸주는 작업을 하니 오류가 해결됐습니다.
설명해준 내용과 비슷하게 맞춰주기 위해 if 라는 것도 한번 감싸주도록 합시다.
저기에서는 e 라는 것에서 getHelath() 메서드를 호출하고 있는데, 우리는 event.getEntity() 라는 것에서 getHleath() 메서드를 호출하고 있으니
답변의 e 대신 event.getEntity() 라는 것을 사용해줬습니다.
그러면 버전을 올려 플러그인을 적용시켜 보도록 하겠습니다.
<공격을 한 대상의 이름> 이 <공격을 받은 대상의 이름> 에게 <데미지> 의 데미지를 입혔습니다.
<공격을 받은 대상의 이름> 의 남은 체력은 <공격받은 대상의 체력> 입니다.
원하는 대로 공격할 때 위와 같은 문구가 노출되는 것을 확인했습니다.
여기서 이상한 점 하나와 알게된 사실이 하나 있습니다.
처음 공격을 했을 때 남은 좀비의 체력이 20.0 이라고 노출 됩니다.
플러그인은 마인크래프트 세상에서 서버로 받은 이벤트를 계산해 결과값으로 돌려주는 것이라고 말했습니다.
그러면 이 이벤트가 발생할 때는 언제일까요 ?
1. 유저가 공격을 하기 전
2. 유저가 공격을 할 때
3. 유저가 공격을 하고 난 후
바로 2번 입니다. 플러그인은 2번의 타이밍에 이벤트를 전달받는 것입니다.
그러면 공격 이벤트가 발생하는 때는 좀비가 공격을 당하는 순간입니다.
공격을 당한 후의 좀비의 체력을 아직 마인크래프트에게 돌려주지 않았으니
그렇기에 남은 체력은 <현재 체력> - <입힐 데미지> 가 되어야 한다는 것입니다.
익숙한 에러가 노출되고 있습니다. 이 역시 double 이라는 것을 string 에 넣으려고 할 때 등장하는 에러 같기에
Double.toString 이라는 것으로 감싸주도록 하겠습니다.
if(event.getEntity() instanceof LivingEntity) {
event.getDamager().sendMessage(
event.getEntity().getName() +
" 의 남은 체력은 " +
Double.toString(((LivingEntity)event.getEntity()).getHealth() - event.getDamage()) +
" 입니다."
);
}
다시 버전을 하나 올려 플러그인을 적용시켜보도록 하겠습니다.
성공적으로 공격을 한 체력이 노출되는 것을 확인할 수 있습니다.
그리고 체력이 0 이하로 떨어졌을 때 좀비가 죽는 것을 확인할 수 있었습니다.
알게 된 사실은
플레이어가 목검으로 좀비를 공격할 경우 목검의 공격력인 4.0 은 맞지만
좀비의 체력이 딱 맞게 떨어지지 않는 것을 확인할 수 있습니다.
조금 앞서 필요한 정보에 대해 미리 전달드리자면 컴퓨터 상에서 소수점의 계산은 정확하지 않을 수 있습니다.
컴퓨터에서 20.0 이라는 소수점에서 4.0 이라는 소수점을 빼는 작업은 16 이라는 정확한 값을 돌려주는 것은 매우 까다로운 일이 될 수 있습니다.
그렇기에 약간의 오차가 발생하고 그것으로 인해 좀비가 계산했던 것보다 한번 더 맞아야 죽는다는 것을 확인할 수 있었습니다.
이것으로 7. 마인크래프트 플러그인 개발 - 데미지 디스플레이 플러그인 개발 은 마치도록 하겠습니다.
'마인크래프트' 카테고리의 다른 글
10. 마인크래프트 플러그인 개발 - 작물 성장 체크 플러그인 개발 (0) | 2024.08.25 |
---|---|
9. 마인크래프트 플러그인 개발 - 챗컬러 플러그인 개발 (0) | 2024.08.24 |
6. 마인크래프트 플러그인 개발 - Listener 활용 (0) | 2024.08.16 |
5. 마인크래프트 플러그인 개발 - Listener (0) | 2024.08.15 |
4. 마인크래프트 플러그인 개발 - 기초 플러그인 개발 (0) | 2024.08.15 |