捕獲処理の作成
前回モンスターボールのクラスを作成したので、今回は捕獲判定までの一連の処理を仕上げていきます。サービス自体は他のアイテムと一緒にするためItemServiceを呼び出し、その中で使用されたアイテムを判断して分岐を作ります。
バトル中のアイテムサービス(/App/Services/Battle/ItemService.php)
<?php
$root_path = __DIR__.'/../../..';
// 親クラス
require_once($root_path.'/App/Services/Service.php');
// トレイト
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleAttackTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleEnemyTurnTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleAttackAfterTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCheckTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleEnemyAiTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleOrderGenelatorTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleExTrait.php');
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCalTrait.php');
require_once($root_path.'/App/Traits/Service/Item/ServiceItemUseTrait.php');
require_once($root_path.'/App/Traits/Service/Item/ServiceItemCaptureTrait.php');
/**
* どうぐの使用
*/
class ItemService extends Service
{
// バトルで使用するトレイト
use ServiceBattleAttackTrait;
use ServiceBattleEnemyTurnTrait;
use ServiceBattleAttackAfterTrait;
use ServiceBattleCheckTrait;
use ServiceBattleEnemyAiTrait;
use ServiceBattleOrderGenelatorTrait;
use ServiceBattleExTrait;
use ServiceBattleCalTrait;
// アイテムで使用するトレイト
use ServiceItemUseTrait;
use ServiceItemCaptureTrait;
/**
* 使用されたアイテム
* @var object::Item
*/
protected $item;
/**
* 捕獲フラグ
* @var boolean
*/
protected $capture_flg = false;
/**
* アイテム使用時のメッセージID
* @var string
*/
protected $use_msg_id;
/**
* @return void
*/
public function __construct()
{
//
}
/**
* @return void
*/
public function execute()
{
// アイテムの確認
if(!$this->validation()){
setMessage('指定されたアイテムは使用できません');
return;
}
// アイテムの使用
$this->use();
if($this->capture_flg){
// 捕獲成功
// バトル終了判定用メッセージの格納
setEmptyMessage('battle-end');
}else{
// 捕獲失敗
//相手のターン処理
$this->enemyTurn();
}
}
/**
* アイテムの検証
* @return boolean
*/
private function validation(): bool
{
// アイテム一覧を取得
$items = player()->getItems();
if(!isset($items[request('order')])){
return false;
}
// 個数チェック(念の為)
if($items[request('order')]['count'] <= 0){
return false;
}
// どうぐをインスタンス化(プロパティへ格納)
$this->item = new $items[request('order')]['class'];
// 指定されたアイテムがポケモン対象の場合は、ポケモン番号をチェック
if(
$this->item->getTarget() === 'friend' &&
is_null(player()->getPartner(request('pokemon')))
){
return false;
}
// バリデーション通過
return true;
}
/**
* 使う
* @return void
*/
private function use(): void
{
// 使用時のメッセージIDを発行
$this->use_msg_id = issueMsgId();
setMessage(
player()->getName().'は、'.$this->item->getName().'を使った',
$this->use_msg_id
);
// アイテムの対象による分岐
switch ($this->item->getTarget()) {
// 味方ポケモン
case 'friend':
$result = $this->useItemToFriend($this->item);
break;
// 相手ポケモン
case 'enemy':
if($this->item->getCategory() === 'ball'){
// 捕獲処理
$result = $this->useItemCapture($this->item);
// 捕獲フラグ
$this->capture_flg = $result;
}else{
$result = $this->useItemToEnemy($this->item);
}
break;
}
// アイテムを1つ消費
if($result){
player()->subItem(request('order'));
}
}
}
ボールアイテムが使用された際には、捕獲(useItemCapture)のメソッドを呼び出して処理、もし捕獲に失敗した際は、逃げる失敗時と同様に相手の行動処理を進めています。
捕獲率の計算
それでは、捕獲率の計算についてです。計算式は前回まとめているので、そちらを参考にしてください。
https://s-yqual.com/blog/1508
計算処理の流れは、1揺れ当たりの成功率(G)を算出し、それを4回(揺れ数)判定、全てクリアすれば捕獲成功の結果を返却します。
それではまず、Gの計算用メソッドを見てみましょう。
捕獲処理用トレイト(/App/Traits/Service/Item/ServiceItemCaptureTrait.php)
<?php
// ボール使用時のトレイト
trait ServiceItemCaptureTrait
{
/**
* 1揺れ辺りの成功率計算処理(G)
* 計算式
* B = (((最大HP×3-現在HP×2)×捕捉率×捕獲補正)÷(最大HP×3))×状態補正
* G = 65536÷(255÷B)^0.1875
* ・MH: 最大HP
* ・RH: 現在HP
* ・C : 捕捉率
* ・P : 捕獲補正
* ・S : 状態補正
* @param ball:object::Item
* @return float:G
*/
protected function calProbability(object $ball): float
{
// 最大HP
$mh = enemy()->getStats('HP');
// 現在HP
$rh = enemy()->getRemainingHp();
// 捕捉率
$c = enemy()->getCapture();
// 捕獲補正
$p = $ball->getPerformance();
/**
* 状態補正
* どく(もうどく)orまひorやけど:1.5
* こおりorねむり:2.5
*/
$sa = enemy()->getSa();
if(in_array($sa, ['SaSleep', 'SaFreeze'], true)){
// こおり or ねむり
$s = 2.5;
}else if($sa){
// どく(もうどく) or まひ or やけど
$s = 1.5;
}else{
// 未状態異常
$s = 1;
}
// (((最大HP × 3 - 現在HP × 2) × 捕捉率 × 捕獲補正) ÷ (最大HP × 3)) × 状態補正
$b = (((($mh * 3) - ($rh * 2)) * $c * $p) / ($mh * 3)) * $s;
// Gを返却
return 65536 / (255 / $b) ** 0.1875;
}
}
算出に必要な値として、相手ポケモンの最大HPと現在HP、捕捉率と状態補正(状態異常の状態)、そして使用したボールの捕獲補正を、それぞれ変数に格納していきます。
あとは、B算出の式に当てはめ、G計算を行うという流れです。
ダメージ計算と同様に、式自体が出来てしまっていれば数値を当てはめていくだけです。返り値は小数点以下を含む値になるので、float型指定で返却しています。
揺れ判定
前処理でG(1揺れ当たりの確率)が算出できるので、あとは4回の判定を行います。Gの値が65535以下であれば捕獲成功になるので、0から65535までの乱数をrandam_intで生成して比較する式をwhileでループ、揺れ回数が4回以上になれば捕獲成功と判断して処理を終了させます。
/**
* 捕獲判定
* @param ball:object::Item
* @return boolean
*/
protected function capture(object $ball): bool
{
// 1揺れ辺りの成功率(G)を算出
$g = $this->calProbability($ball);
// 4揺れ判定(0の可能性もある)
// 0000〜FFFFの乱数を取る
while (random_int(0, 65535) <= $g) {
$this->shake++;
// 揺れが4以上になれば捕獲成功
if($this->shake >= 4){
$capture = true;
break;
}
}
// 結果を返却
return $capture ?? false;
}
揺れ回数は、ボール使用時の演出でも使用するためプロパティに格納して、最終的な返り値として使用します。返り値に指定している$captureが存在していなければ、捕獲まで到達していないということが判断できるので、null合体演算子でfalseを返し、捕獲処理自体が成功したかどうかを返却することが可能です。
パーティーへの追加
捕獲処理が成功すれば、その時点で相手ポケモンをパーティーへ追加、バトルを強制終了させます。現在はボックスの概念が存在していないので、直接パーティーへ格納しますが、もしボックス枠が確定すれば、パーティー数に空きがなければボックスへ転送するという分岐を追加します。
/**
* ポケモンの登録
* @return void
*/
protected function storePokemon(): void
{
// 立場を変更
enemy()->setPosition();
// パーティーセット
player()->setParty(enemy());
}
パーティーやボックスにポケモンを自由に追加出来てしまわないように、捕まえた際には相手ポケモンの立場をfriendに変更してからセットします。setPartyでは、立場がfriendでなければ格納できないように制限をかけておきましょう。
これでボールの使用からパーティーへの追加までの一連の処理が完成です。以下捕獲処理用トレイトのまとめです。
捕獲処理用トレイト(/App/Traits/Service/Item/ServiceItemCaptureTrait.php)
<?php
// ボール使用時のトレイト
trait ServiceItemCaptureTrait
{
/**
* 揺れ回数
* @var integer
*/
protected $shake = 0;
/**
* 捕獲判定
* @param ball:object::Item
* @return boolean
*/
protected function useItemCapture(object $ball): bool
{
// 捕獲判定
$result = $this->capture($ball);
// レスポンス
setResponse([
'action' => 'capture',
'param' => json_encode([
'shake' => $this->shake,
'src' => '/Assets/img/item/class/'.get_class($ball).'.png'
])
], $this->use_msg_id);
// 判定
if($result){
// 捕獲成功
setResponse(true, 'result');
setMessage('やったー!'.enemy()->getName().'を捕まえた!');
// 捕まえたポケモンを登録
$this->storePokemon();
}else{
// 捕獲失敗
setResponse(false, 'result');
// 揺れ回数に合わせてメッセージ分岐
$msg = '残念、ポケモンがボールから出てしまった';
if($this->shake === 2){
$msg = 'ああ、捕まえたと思ったのに';
}
if($this->shake === 3){
$msg = '惜しい、あとちょっとのところだったのに';
}
setMessage($msg);
}
// 結果を返却
return $result;
}
/**
* 捕獲判定
* @param ball:object::Item
* @return boolean
*/
protected function capture(object $ball): bool
{
// 1揺れ辺りの成功率(G)を算出
$g = $this->calProbability($ball);
// 4揺れ判定(0の可能性もある)
// 0000〜FFFFの乱数を取る
while (random_int(0, 65535) <= $g) {
$this->shake++;
// 揺れが4以上になれば捕獲成功
if($this->shake >= 4){
$capture = true;
break;
}
}
// 結果を返却
return $capture ?? false;
}
/**
* 1揺れ辺りの成功率計算処理(G)
* 計算式
* B = (((最大HP×3-現在HP×2)×捕捉率×捕獲補正)÷(最大HP×3))×状態補正
* G = 65536÷(255÷B)^0.1875
* ・MH: 最大HP
* ・RH: 現在HP
* ・C : 捕捉率
* ・P : 捕獲補正
* ・S : 状態補正
* @param ball:object::Item
* @return float:G
*/
protected function calProbability(object $ball): float
{
// 最大HP
$mh = enemy()->getStats('HP');
// 現在HP
$rh = enemy()->getRemainingHp();
// 捕捉率
$c = enemy()->getCapture();
// 捕獲補正
$p = $ball->getPerformance();
/**
* 状態補正
* どく(もうどく)orまひorやけど:1.5
* こおりorねむり:2.5
*/
$sa = enemy()->getSa();
if(in_array($sa, ['SaSleep', 'SaFreeze'], true)){
// こおり or ねむり
$s = 2.5;
}else if($sa){
// どく(もうどく) or まひ or やけど
$s = 1.5;
}else{
// 未状態異常
$s = 1;
}
// (((最大HP × 3 - 現在HP × 2) × 捕捉率 × 捕獲補正) ÷ (最大HP × 3)) × 状態補正
$b = (((($mh * 3) - ($rh * 2)) * $c * $p) / ($mh * 3)) * $s;
// Gを返却
return 65536 / (255 / $b) ** 0.1875;
}
/**
* ポケモンの登録
* @return void
*/
protected function storePokemon(): void
{
// 立場を変更
enemy()->setPosition();
// パーティーセット
player()->setParty(enemy());
}
}
揺れ回数に合わせてメッセージが異なるので、失敗時には回数に合わせてメッセージを用意しています。
また、フロント処理用のレスポンスでは、ボール画像パスを格納することで、使用したボールに合わせて変化を持たせています。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは、モンスターボール使用時の判定からゲットまでの処理についてご紹介しました。
次回はボール使用時の演出について紹介します。乞うご期待ください!