AwaitFormOptions

AwaitFormOptions v3.0.0

pmmp日本界隈の皆さんお元気ですか?
早速ですが、既存のフォームライブラリは、フォームシーケンスが作りづらくて仕方ない!
なので、AwaitGeneratorとAwaitFormを悪魔合体したフォームライブラリ書きました。
詳しくはGithubに行ってReadmeを見てほしいですが、ここでは特徴を述べます。
ただ、書いた私も悩むぐらい難しいので、pmmp中級者以上の方向けです。

特徴1
フォームの部品一つ一つがオプションという単位に分割されており、使いまわせる。
フォームキーの管理?必要ありません。
AwaitFormOptionsのフォーム要素の最小単位はオプションであり、各オプションにはそれぞれ処理を組み込めます。
言葉で言ってもわかりずらいので、例を見てみましょう

PHP:
use pocketmine\plugin\PluginBase;
use pocketmine\event\Listener;
use pocketmine\event\player\PlayerItemUseEvent;
use SOFe\AwaitGenerator\Await;
use DaisukeDaisuke\AwaitFormOptions\AwaitFormOptions;
use DaisukeDaisuke\AwaitFormOptions\exception\AwaitFormOptionsParentException;
use pocketmine\form\FormValidationException;
use cosmicpe\awaitform\AwaitForm;

//plugin関連をここに挿入

  public function a(PlayerItemUseEvent $event) : void{
      $player = $event->getPlayer();
      Await::f2c(function() use ($player){
          try{
              yield from AwaitFormOptions::sendFormAsync(
                  player: $player,
                  title: "test",
                  options: [
                      new HPFormOptions($player),
                  ]
              );
          }catch(FormValidationException|AwaitFormOptionsParentException){
              // Form failed validation
          }
      });
  }
まず、Await::f2c(function() use ($player){でAwaitGeneratorの並行処理に突入し、
sendFormAsyncという処理をyield fromして応答を待機します。
エラーは例外という形で受け取ります。

PHP:
<?php

declare(strict_types=1);


namespace test\test;

use DaisukeDaisuke\AwaitFormOptions\FormOptions;
use cosmicpe\awaitform\FormControl;
use pocketmine\player\Player;
use DaisukeDaisuke\AwaitFormOptions\exception\AwaitFormOptionsChildException;

class HPFormOptions extends FormOptions{
    public function __construct(private Player $player){
    }

    public function maxHP() : \Generator{
        try{
            $form = [
                FormControl::input("Max HP:", "20", (string) $this->player->getMaxHealth()),
            ];
            [$maxHP] = yield from $this->request($form); // awaiting response
            $this->player->setMaxHealth((int) $maxHP);
            $this->player->sendMessage("Max HP: {$maxHP}");
        }catch(AwaitFormOptionsChildException $e){
            var_dump($e->getCode());
        }
    }

    public function currentHP() : \Generator{
        try{
            $form = [
                FormControl::input("Current HP:", "20", (string) $this->player->getHealth()),
            ];
            [$currentHP] = yield from $this->request($form); // awaiting response
            $this->player->setHealth((float) $currentHP);
            $this->player->sendMessage("Current HP: {$currentHP}");
        }catch(AwaitFormOptionsChildException $e){
            var_dump($e->getCode());
        }
    }

    public function getOptions() : array{
        return [
            $this->maxHP(),
            $this->currentHP(),
        ];
    }

    public function userDispose() : void{
        unset($this->player);
    }
}
オプションクラスは、getOptions、userDisposeで構成されるシンプルな処理であり、 getOptions()でジェネレーターを返せばそのオプションが実行され、yield from $this->request()で指定したフォームがgetOptionsで渡した順番上通りにフォームに追加されます。
各オプションは、個別に指定したオプションの応答をyield fromから受け取り、必要に応じてreturnすることで親に値を返すことができます。
エラーが発生した場合は、親ジェネレーターも子ジェネレーターももれなく例外を受け取ります。もうぬるぽにおびえる必要はないぞ
で、このオプションクラス、複数の親フォームで使いまわせます。既存のフォームは、使いまわせる最小単位がフォーム単位でしたか、AwaitFormOptionsではこれを「オプション」という単位に分割化することとしました。

特徴2
フォームのキーは重複セーフ

フォームを使いまわすときに、同じキーが重複することに怯えてる?心配ありません。
AwaitFormOptionsはキー重複セーフです。

例えばこんなわけわからんフォームでも、AwaitFormOptionsは正しく処理します

PHP:
public function a(PlayerItemUseEvent $event): void {
    $player = $event->getPlayer();
    Await::f2c(function () use ($player) : \Generator{
        try {
            yield from AwaitFormOptions::sendMenuAsync(
                player: $player,
                title: "test",
                content: "a",
                buttons: [
                    new NameMenuOptions($player, ["a", "b"]),
                    new NameMenuOptions($player, ["c", "d"]),
                    new NameMenuOptions($player, ["e", "f"]),
                    new NameMenuOptions($player, ["g", "h"]),
                    new NameMenuOptions($player, ["i", "j"]),
                ]
            );
        } catch (FormValidationException) {
        }
    });
}
特徴3

オプションは、値を返すことができる
例を見てみましょう
親オプションの配列でキーを指定すると、その通りにマッピングされた返り値を受け取ります。
例外が発生したときは、返り値は受け取れません。

PHP:
public function onUse(PlayerItemUseEvent $event): void{
    $player = $event->getPlayer();
    if(!$player->isSneaking()){
        return;
    }
    Await::f2c(function() use ($player) {
        try {
            $selected = yield from AwaitFormOptions::sendFormAsync(
                player: $player,
                title: "test",
                options: [
                    "input1" => new SimpleInput("test1", "test", "test", 0),
                    "input2" => new SimpleInput("test2", "test2", "test2", 0),
                ]
            );
            var_dump($selected);
        } catch (FormValidationException|AwaitFormOptionsParentException) {
            // The form was cancelled or failed
        }
    });
}
子ジェネレーターはyield from $this->requestから応答を受け取りますが、それを自由に加工してreturnすることができます。今回は配列の最初を返します。



PHP:
<?php

declare(strict_types=1);

namespace test\test;

use DaisukeDaisuke\AwaitFormOptions\FormOptions;
use cosmicpe\awaitform\FormControl;

class SimpleInput extends FormOptions{
    public function __construct(private string $text, private string $default, private string $placeholder, private int $id){
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function input(int $offset) : \Generator{
        $output = yield from $this->request([FormControl::input($this->text, $this->default, $this->placeholder), $this->id + $offset]);
        return $output[array_key_first($output)];
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function getOptions() : array{
        return [
            $this->input(0),
            $this->input(1),
        ];
    }
    public function userDispose() : void{
        unset($this->text, $this->default, $this->placeholder, $this->id);
    }
}
すると、ジェネレーターからのすべてのreturnが収集され、親ジェネレーターはoptionsとgetOptionsで指定した通りのキーを持つ配列を受け取ります。

Code:
array(2) {
  ["input1"]=>
  array(2) {
    [0]=>
    string(4) "test"
    [1]=>
    string(4) "test"
  }
  ["input2"]=>
  array(2) {
    [0]=>
    string(5) "test2"
    [1]=>
    string(5) "test2"
  }
}
特徴4

フォームはネスト可能

1階層までに制限されてますが、getOptionsはフォームオプションを指定しても大丈夫です。
この場合、returnは親が受け取ります。
なんでもありですね

PHP:
public function onUse(PlayerItemUseEvent $event) : void{
    $player = $event->getPlayer();
    if(!$player->isSneaking()){
        return;
    }
    Await::f2c(function() use ($player){
        while(true){
            try{
                $result = yield from AwaitFormOptions::sendFormAsync(
                    player: $player,
                    title: "Confirmation",
                    options: ["output" => new ConfirmInputForm()]
                );
                var_dump($result);
                //generator returns
                $typed = $result["output"]["confirm"];
                if(strtolower(trim($typed)) === "yes"){
                    $player->sendToastNotification("Confirmed", "Thanks for typing!");
                    break;
                }

            }catch(AwaitFormOptionsParentException $exception){
                if($exception->getCode() !== AwaitFormException::ERR_PLAYER_REJECTED){
                    break;
                }
            }
            $player->sendToastNotification("You must type 'yes'.", "please Type 'Yes'");
        }
    });
}
PHP:
<?php

declare(strict_types=1);


namespace test\test;

use DaisukeDaisuke\AwaitFormOptions\FormOptions;
use cosmicpe\awaitform\FormControl;
use DaisukeDaisuke\AwaitFormOptions\exception\AwaitFormOptionsChildException;

class ConfirmInputForm extends FormOptions{
    /**
     * @throws AwaitFormOptionsChildException
     */
    public function confirmOnce(): \Generator {
        [$input] = yield from $this->request([
            FormControl::input("Type 'yes' to confirm", "yes", ""),
        ]);
        return $input;
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function getOptions(): array {
        return [
            "entity" => new SimpleInput("nested!", "nested", "nested", 0),
            "confirm" => $this->confirmOnce(),
        ];
    }

    public function userDispose() : void{

    }
}
Code:
array(1) {
  ["output"]=>
  array(2) {
    ["entity"]=>
    array(2) {
      ["a"]=>
      string(6) "a"
      [0]=>
      string(6) "nested"
    }
    ["confirm"]=>
    string(3) "yes"
  }
}
特徴6

便利なシステム関数

フォームを送信する前に非同期のデータベースクエリをしたい場合はどうすればいいですか?
他のフォームはこれはできず、クロージャー地獄に頼る必要があります。
AwaitFormOptions?心配ありません。$this->schedule();がここにあります!

子ジェネレーターで$this->schedule();を呼ぶと、ご丁寧に予約した子ジェネレーターが$this->requestするまでフォームの送信を延期してくれます。

PHP:
<?php

namespace daisukedaisuke\test;

use cosmicpe\awaitform\Button;
use DaisukeDaisuke\AwaitFormOptions\MenuOptions;
use SOFe\AwaitGenerator\RaceLostException;

class HpBasedFoodOptions extends MenuOptions{
    /**
     * @throws AwaitFormOptionsChildException
     */
    public function giveRawFish1() : \Generator{
        $this->schedule(); // This ensures that the awaitformoptions coroutine is temporarily suspended
        //A few awaits
        try{
            yield from $this->request([Button::simple("a"), 0]); // Here, the suspension is lifted
        }catch(RaceLostException){
            var_dump("??");
        }
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function getOptions() : array{
        return [
            $this->giveRawFish1(),
        ];
    }
  
    public function userDispose() : void{
      
    }
}

フォームの結果をフラットなオブジェクト指向ですべて収集して、最終処理を子ジェネレーターでしたい場合どうすればいいですか?
AwaitFormOptionsでは心配ありません。yield from $this->finalize(10000);があります!

PHP:
<?php

namespace test\test;

use DaisukeDaisuke\AwaitFormOptions\FormOptions;
use cosmicpe\awaitform\FormControl;
use DaisukeDaisuke\AwaitFormOptions\exception\AwaitFormOptionsChildException;

class ConfirmInputForm extends FormOptions{
    /**
     * @throws AwaitFormOptionsChildException
     */
    public function confirmOnce() : \Generator{
        [$input] = yield from $this->request([
            FormControl::input("Type 'yes' to confirm", "yes", ""),
        ]);
        yield from $this->finalize(10000);//Awaiting other generators with priority 10000
        return $input;
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function getOptions() : array{
        return [
            "entity" => new SimpleInput("nested!", "nested", "nested", 0),
            "confirm" => $this->confirmOnce(),
        ];
    }

    public function userDispose() : void{

    }
}
yield from $this->finalize(0)を実行すると、すべての子ジェネレーターに結果がいきわたり、処理が終了した後にコルーチンが再開されます!

特徴7

条件に応じてメニューのボタンや、オプションが消えるフォーム?
心配ありません。
getOptionsで条件分岐するだけです。

PHP:
<?php

declare(strict_types=1);


namespace test\test;


use pocketmine\player\Player;
use pocketmine\item\VanillaItems;
use cosmicpe\awaitform\Button;
use DaisukeDaisuke\AwaitFormOptions\MenuOptions;
use DaisukeDaisuke\AwaitFormOptions\exception\AwaitFormOptionsChildException;

class HpBasedFoodOptions extends MenuOptions{

    public function __construct(private Player $player){
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function giveRawFish() : \Generator{
        yield from $this->request([
            Button::simple("§2You are full of strength! Enjoy this raw fish.§r"),
        ]);
        $this->player->getInventory()->addItem(VanillaItems::RAW_FISH()->setCount(1));
        $this->player->sendToastNotification("Food Given", "Raw Fish");
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function giveCookedFish() : \Generator{
        yield from $this->request([
            Button::simple("§6You're moderately hurt. Take this cooked fish.§r"),
        ]);
        $this->player->getInventory()->addItem(VanillaItems::COOKED_FISH()->setCount(1));
        $this->player->sendToastNotification("Food Given", "Cooked Fish");
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function giveSteak() : \Generator{
        yield from $this->request([
            Button::simple("§4You're starving! Here's a juicy steak.§r"),
        ]);
        $this->player->getInventory()->addItem(VanillaItems::STEAK()->setCount(1));
        $this->player->sendToastNotification("Food Given", "Steak");
    }

    /**
     * @throws AwaitFormOptionsChildException
     */
    public function getOptions() : array{
        $hp = $this->player->getHealth();

        $result = [];
        if($hp <= 20){
            $result[] = $this->giveRawFish();
        }
        if($hp <= 10){
            $result[] = $this->giveCookedFish();
        }
        if($hp <= 5){
            $result[] = $this->giveSteak();
        }
        return $result;
    }

    public function userDispose() : void{
        unset($this->player);
    }
}
フォームで利用可能なエレメント

PHP:
FormControl::divider() // Adds a horizontal divider to visually separate form sections.
FormControl::dropdown(string $label, array $options, ?string $default = null) // Select from a list of options, returns the selected value.
FormControl::dropdownIndex(string $label, array $options, int $default = 0) // Select from a list of options, returns the selected index.
FormControl::dropdownMap(string $label, array $options, array $mapping, mixed $default = null) // Select from a list of options, returns a mapped value.
FormControl::header(string $label) // Adds a bold header text to highlight sections.
FormControl::input(string $label, string $placeholder = "", string $default = "") // Text input field. Returns user input as a string.
FormControl::label(string $label) // Static text label, for descriptions or instructions.
FormControl::slider(string $label, float $min, float $max, float $step = 0.0, float $default = 0.0) // A numeric slider. Returns a float value.
FormControl::stepSlider(string $label, array $steps, ?string $default = null) // A discrete slider with string options. Returns a selected step.
FormControl::toggle(string $label, bool $default = false) // A boolean toggle (checkbox). Returns true/false.

メニューで利用可能なエレメント

PHP:
Button::simple(string $text) // One user selectable button with text
特徴8

複雑なフォームシーケンス?怯える必要はありません。
エラーがあれば例外で中断するため、応答がnullであるかどうかを確認する必要は一切ありません

PHP:
Await::f2c(static function() use($array, $owner){
                try{
                    $array[$owner->getId()] = $owner;
                    [$array] = yield from AwaitFormOptions::sendFormAsync(
                        player: $owner,
                        title: "デバック棒",
                        options: [
                            new PlayerOREntitySelectionOptions($array),
                        ],
                    );
                    /** @var ?Entity $entity */
                    $entity = $array["Entity"];

                    $name = $entity instanceof Player ? $entity->getName() : substr(strrchr($entity::class, "\\"), 1);

                    $menus = [
                        new DebugKillFrom(),
                        //mobをぶっ飛ばすやつ
                        //ゲームから強制退室
                        //ゲーム参加からタイムアウト
                    ];

                    $result = [];
                    foreach($menus as $menu){
                        $result[] = new SimpleMappedButtonOptions($menu->getExplanation(), $menu);
                    }

                    /** @var DebugStickMenuInterface|FormOptions $form */
                    $form = yield from AwaitFormOptions::sendMenuAsync(
                        player: $owner,
                        title: $name . "さんに対してあんなことやこんなことができます!",
                        content: "",
                        buttons: $result
                    );

                    if(!$entity->isAlive() || ($entity instanceof Player && !$entity->isOnline())){
                        if($owner->isOnline()){
                            $owner->sendToastNotification("おーっと、プレーヤーがオフラインになってしまったようです。", $name);
                        }
                    }

                    $form->setTarget($entity);

                    yield from AwaitFormOptions::sendFormAsync(
                        player: $owner,
                        title: $name,
                        options: [$form],
                    );
                }catch(FormValidationException | AwaitFormOptionsParentException){
                    return;
                }
            });

GithubのReadmeで詳しく解説してるので、よかったら見てね
著者
DaisukeDaisuke
ダウンロード数
1
閲覧数
28
最初のリリース
最後の更新
評価
0.00 つ星 評価0

More resources from DaisukeDaisuke