リファクタリング①「基礎用語説明あり」

onCommandをなくす

if文をなくす

  • 「onCommand」= コマンドを実行したらということ。
  • if文からスタートすることは、気持ち悪い。
  • プレイヤーが実行することが前提なので、あえて前提条件を用意するのではなく、隠しておきたい。
  • プレイヤーが実行することはわかっているのだから、あえて、プレイヤーが実行したらという前提はいらない。
  • もし、プレイヤー以外が実行したら「return false」で何も実行しない指示となっている。
  • プログラムの中身の実行したい処理のことを「ドメインロジック」という。
  • Sipgotが用意してくれていないのであれば自分でやろうということをする。
  • コンソールとは、コマンドを使わずに、他の方法で指示を送るという認識。
  • 最初の「if」文は、spigotの使用上、こうしなければいけないのでしているという要素となっている。
  • 「もし、実行しているのがプレイヤーだったら」ということを書かなければいけないのは、ややこしくしてしまうだけとなる可能性がある。

リファクタリング

まず、「command」ファイルの中に、「BaseCommand」クラスを新規に作ります。

そして、クラスの中に何をするものであるか解るようにするため、ジャバドッグを書き込む

package plugin.enemydown.command;

/**
 * コマンドを実行して動かすプラグイン処理の基底クラスです。
 */
public class BaseCommand {

}

とする。

ここで、「EnemyDownCommand」の「implements CommandExecutor」の部分をコピーしてきて

public class BaseCommand implements CommandExecutor {

}

とします。

すると、「onCommand」を実装してくれと出るので、エラーの波線にカーソルをあて「メソッドの実装」で、そのまま出てきたものをエンターして

お約束として、「CommandExecutor」は「このクラスを使うなら、onCommandという特別なルール(メソッド)を必ず書いてね」というお約束を持っています。たとえば、こんな感じで、「CommandExecutor」は「コマンドを実行するための設計図」のようなもで、この設計図には「onCommand」という名前の部品が必要だよ、となっている。

もし、その部品(onCommandメソッド)を書かないと、「設計図通りに作られていないよ!」とプログラムが怒るようになる。

public class BaseCommand implements CommandExecutor {

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    return false;
  }
}

となります。

「boolean」=「true」か「false」のプログラムを書くときに使用するもの???という理解😂

そこで、「EnemyDownCommand」クラスの中の「if(sender instanceof Player player)」の部分をコピぺして

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      
    }
    return false;
  }
}

とする。

ここで、

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      return true;
    }
    return false;
  }

としておいて、処理が終わったら、「自分が指定する条件を満たしていれば、「return true」(正解)として処理しなさい」という処理を付け加えることができるようにする。
逆に「return false」にすると、この場合は、それ以外のときは何もしないという指示を送ることになる。

例えば、落ちてくるものが岩石ならば避けなさい。(return true)
落ちてくるものが、お金ならばそのままいて「何もしないでかごの中に入るようにする。」のようなイメージ??

そして

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      return true;
    }else {
      return true;
    }
  }
}

とすることで、上の「return true」で〇〇したときは、〇〇を実行しなさい、そして、下の「return true」で、そうではなくて△△したときは、△△しなさい。という指示を遅れるようになる。

そして、「onExecutePlayerCommand(player);」
「プレイヤーが何かの命令(コマンド)をしたら、それに応じて特別な仕事をするよ!」という指示。
を入力すると

このコマンド名のものがないので、エラーが出ているので、エラーの部分にカーソルをあて、メソッドの作成を
クリックすると、

「private void onExecutePlayerCommand(Player player) {}」ができるので

「public abstract boolean onExecutePlayerCommand(Player player) {}」とする。

public class BaseCommand implements CommandExecutor {

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      onExecutePlayerCommand(player);
      return true;
    }else {
      return true;
    }
  }

  public abstract boolean onExecutePlayerCommand(Player player) {
  }
}

ここで、public abstract boolean onExecutePlayerCommand(Player player) {}の{}は削除し「;」を追加する
ここの部分は、動画と違うやり方となる、

そこで、「if」文の中身に戻り、

「 onExecutePlayerCommand(player);
return true;」の部分を

public class BaseCommand implements CommandExecutor {

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      return onExecutePlayerCommand(player);
    }else {
      return true;
    }
  }

  public abstract boolean onExecutePlayerCommand(Player player) {
  }
}

こう変える。

return onExecutePlayerCommand(player)=onExecutePlayerCommand(player)をした結果が返ってくるという意味。

boolean onCommand=コマンドを実行した結果という意味。

abstract=抽象クラスといわれるもの。
(基本のルールブックのような物で、みんなで使う前提的なもののみ指定する。具体的なことは、ちがうところで指示していく。)
このときは、クラスにもつけて「abstract class」としなければならない。

ここでは、コマンドを実行したのがプレイヤーだった場合に何をするかを定義している。

そして、このあと、プレーヤーではなかった場合を定義するので

}else {
      return true;
    }

の部分を

return onExecuteNPCCommand(sender);

と変え、これも同じように、コマンドがないといわれるので、エラー箇所にカーソルをあてメソッドを作り

/**
 * コマンドを実行して動かすプラグイン処理の基底クラスです。
 */
public abstract class BaseCommand implements CommandExecutor {

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      return onExecutePlayerCommand(player);
    }else {
      return onExecuteNPCCommand(sender);
    }
  }

  public abstract boolean onExecutePlayerCommand(Player player);

  public abstract boolean onExecuteNPCCommand(CommandSender sender);
}

とする。

ここで、説明文を書いて、
「BaseCommand」は

package plugin.enemydown.command;

import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

/**
 * コマンドを実行して動かすプラグイン処理の基底クラスです。
 */
public abstract class BaseCommand implements CommandExecutor {

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){
      return onExecutePlayerCommand(player);
    }else {
      return onExecuteNPCCommand(sender);
    }
  }

  /**
   * コマンド実行者がプレイヤーだった場合に実行します。
   * @param player コマンドを実行したプレイヤー
   * @return 処理の実行有無
   */
  public abstract boolean onExecutePlayerCommand(Player player);

  /**
   * コマンドの実行者がプレイヤー以外だった場合に実行します。
   * @param sender コマンドの実行者
   * @return 処理の実行有無
   */
  public abstract boolean onExecuteNPCCommand(CommandSender sender);
}

こうなります。

ここで、このコマンドを活用するために「EnemyDownCommand」に継承させるようにする。

「EnemyDownCommand」クラスに移り、

「public class EnemyDownCommand implements CommandExecutor, Listener, org.bukkit.event.Listener」
の部分の「CommandExecutor」はいらなくなるので、消去する。

「CommandExecutor」=プログラムの中で「言われたことをちゃんと実行する係」として働いているもの。

そして
「public class EnemyDownCommand extends BaseCommand implements Listener, org.bukkit.event.Listener」と書き換える。

「extends 」=継承しなさいという意味

ここで、エラーが出てメソッドを実装しろと出るので、カーソルをあてメソッドを実装する。

  @Override
  public boolean onExecutePlayerCommand(Player player) {
    return false;
  }

  @Override
  public boolean onExecuteNPCCommand(CommandSender sender) {
    return false;
  }

ができる。

そこで、「BaseCommand」を継承するようになったので、「BaseCommand」で作成した処理を移せるようになるので、

     PlayerScore nowPlayer = getPlyerScore(player);

      nowPlayer.setGameTime(20);
      //前提条件
      World world = player.getWorld();

      initPlayerStatus(player);

      Bukkit.getScheduler().runTaskTimer(main,Runnable->{
        if (nowPlayer.getGameTime() <= 0){
          Runnable.cancel();
          player.sendTitle("ゲームが終了しました。",
              nowPlayer.getPlayerName() + " 合計 " + nowPlayer.getScore() + "点",
              0,60,0);
          nowPlayer.setScore(0);
          List<Entity> nearbyEnemies = player.getNearbyEntities(50, 0, 50);
          for (Entity enemy : nearbyEnemies){
            switch (enemy.getType()) {
              case ZOMBIE, HUSK, SKELETON -> enemy.remove();
            }
          }
          return;
        }
        world.spawnEntity(getEnemySpawnlocation(player, world), getEnemy());
        nowPlayer.setGameTime(nowPlayer.getGameTime() - 5);
      },0,5*20);

の部分を、「EnemyDownCommand」クラスの中に作成したメソッドのプレイヤーが実行した場合の処理の中に移す。

@Override
  public boolean onExecutePlayerCommand(Player player) {
    PlayerScore nowPlayer = getPlyerScore(player);

    nowPlayer.setGameTime(20);
    //前提条件
    World world = player.getWorld();

    initPlayerStatus(player);

    Bukkit.getScheduler().runTaskTimer(main,Runnable->{
      if (nowPlayer.getGameTime() <= 0){
        Runnable.cancel();
        player.sendTitle("ゲームが終了しました。",
            nowPlayer.getPlayerName() + " 合計 " + nowPlayer.getScore() + "点",
            0,60,0);
        nowPlayer.setScore(0);
        List<Entity> nearbyEnemies = player.getNearbyEntities(50, 0, 50);
        for (Entity enemy : nearbyEnemies){
          switch (enemy.getType()) {
            case ZOMBIE, HUSK, SKELETON -> enemy.remove();
          }
        }
        return;
      }
      world.spawnEntity(getEnemySpawnlocation(player, world), getEnemy());
      nowPlayer.setGameTime(nowPlayer.getGameTime() - 5);
    },0,5*20);
    return true;
  }

とする。(return trueとしておく。)

そうすると、今移してしまった

  @Override
  public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
    if(sender instanceof Player player){



    }
      return false;
  }

は、不要となり、削除する。

Listenerは待ち構えている役割:何か(イベント)が起きるまでじっと待ってイベントが起きたら動く
:例えば、ボタンがクリックされたり、キーが押されたりしたら、それに応じて動く。
Listenerはプログラムの中で「見張り役」として、とても大切な役割を果たしているもの。

「BaseCommand」は、「基本的にこのコマンドを使え」というものになるように、定義して作ることにしたので、様々な機能をもたせると、あとに使うときにいるものやいらないものがいりまじって使い勝手が悪くなる。

ここで、コードが正しいか実証!!

問題なく動きました。

成功!!!

最終的にコードは、こうなりました。

「EnemyDownCommand」

package plugin.enemydown.command;

import java.net.http.WebSocket.Listener;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.SplittableRandom;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import plugin.enemydown.Main;
import plugin.enemydown.data.PlayerScore;

public class EnemyDownCommand extends BaseCommand implements Listener, org.bukkit.event.Listener {

private Main main;
private List<PlayerScore> playerScoreList = new ArrayList<>();

  public EnemyDownCommand(Main main) {
    this.main = main;
  }

  @Override
  public boolean onExecutePlayerCommand(Player player) {
    PlayerScore nowPlayer = getPlyerScore(player);

    nowPlayer.setGameTime(20);
    //前提条件
    World world = player.getWorld();

    initPlayerStatus(player);

    Bukkit.getScheduler().runTaskTimer(main,Runnable->{
      if (nowPlayer.getGameTime() <= 0){
        Runnable.cancel();
        player.sendTitle("ゲームが終了しました。",
            nowPlayer.getPlayerName() + " 合計 " + nowPlayer.getScore() + "点",
            0,60,0);
        nowPlayer.setScore(0);
        List<Entity> nearbyEnemies = player.getNearbyEntities(50, 0, 50);
        for (Entity enemy : nearbyEnemies){
          switch (enemy.getType()) {
            case ZOMBIE, HUSK, SKELETON -> enemy.remove();
          }
        }
        return;
      }
      world.spawnEntity(getEnemySpawnlocation(player, world), getEnemy());
      nowPlayer.setGameTime(nowPlayer.getGameTime() - 5);
    },0,5*20);
    return true;
  }

  @Override
  public boolean onExecuteNPCCommand(CommandSender sender) {
    return false;
  }

  @EventHandler
  public void onEnemyDeath(EntityDeathEvent e){
    LivingEntity enemy = e.getEntity();
    Player player = enemy.getKiller();
    if (Objects.isNull(player) || playerScoreList.isEmpty()) {
      return;
    }

    for(PlayerScore playerScore : playerScoreList){
      if (playerScore.getPlayerName().equals(player.getName())){
        int point = switch (enemy.getType()) {
          case ZOMBIE -> 10;
          case SKELETON, HUSK -> 20;
          default -> 0;
        };

        playerScore.setScore(playerScore.getScore() + point);
        player.sendMessage("敵を倒した! 現在のスコアは" + playerScore.getScore() + "点");
      }
    }
  }

  /**
   * 現在実行しているプレイヤーのスコア情報を取得する。
   *
   * @param player コマンドを実行したプレイヤー
   * @return 現在実行しているプレイヤー情報
   */
  private PlayerScore getPlyerScore(Player player) {
    if(playerScoreList.isEmpty()){
      return addNewPlayer(player);
    }else {
      for(PlayerScore playerScore : playerScoreList){
        if(!playerScore.getPlayerName().equals(player.getName())){
          return addNewPlayer(player);
        }else{
          return playerScore;
        }
      }
    }
    return null;
  }

  /**
   * 新規のプレイヤー情報をリストに追加します。
   *
   * @param player コマンドを実行したプレイヤー
   * @return 新規プレイヤー
   */
  private PlayerScore addNewPlayer(Player player) {
    PlayerScore newPlayer = new PlayerScore();
    newPlayer.setPlayerName(player.getName());
    playerScoreList.add(newPlayer);
    return newPlayer;
  }

  /**
   * ゲームを始める前にプレイヤーの状態を設定する。
   * 体力と空腹度を最大にして、装備はネザライト一式になる。
   *
   * @param player コマンドを実行したプレイヤー
   */
  private void initPlayerStatus(Player player) {

    player.setHealth(20);
    player.setFoodLevel(20);

    PlayerInventory inventory = player.getInventory();
    inventory.setHelmet(new ItemStack(Material.NETHERITE_HELMET));
    inventory.setChestplate(new ItemStack(Material.NETHERITE_CHESTPLATE));
    inventory.setLeggings(new ItemStack(Material.NETHERITE_LEGGINGS));
    inventory.setBoots(new ItemStack(Material.NETHERITE_BOOTS));
    inventory.setItemInMainHand(new ItemStack(Material.NETHERITE_SWORD));
  }

  /**
   *敵の出現場所を取得します。
   * 出現エリアはX軸とZ軸は、自分の一からプラス、ランダムで−10〜9の値がsettingされます。
   * y軸はプレイヤーと同じ位置になります。
   *
   * @param player コマンドを実行したプレイヤー
   * @param world コマンドを実行したプレヤーが所属するワールド。
   * @return 敵の出現場所
   */

  private Location getEnemySpawnlocation(Player player, World world) {
    Location playerLocation = player.getLocation();
    int randomX= new SplittableRandom().nextInt(20) - 10;
    int randomZ= new SplittableRandom().nextInt(20) - 10;

    double x = playerLocation.getX()+randomX;
    double y = playerLocation.getY();
    double z = playerLocation.getZ()+randomZ;

    return new Location(world, x, y, z);
  }

  /**
   * ランダムで敵を抽出して、その結果の敵を取得します。
   * <p>
   * @return 敵
   */
  private EntityType getEnemy() {
    List<EntityType> enemyList =  List.of(EntityType.ZOMBIE, EntityType.SKELETON, EntityType.HUSK);
    int random= new SplittableRandom().nextInt(enemyList.size());
    return enemyList.get(random);
  }
}

「BaseCommand」は、前に記述した通り。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次