"禁止事項"の無い脱出マップ

この記事は [Minecraft Command Advent Calendar 2023](https://qiita.com/advent-calendar/2023/mccmd)  9 日目の記事です。

# もんだい！

「スタート！」から「ゴール！」へ向かおう！
通り道のアルファベットが言葉になるかも！？
**ただし、以下の禁止事項を犯した場合、回答を無効とする可能性があります。**
- 一度通ったマスを通ること
- 壁を通り抜けること
- 僅かな隙間を狙って文字を避けて通ること
- 迷路の外を通ること
- クリーパーのいるマスを通ること
- アイテムのクラフト
- コマンドの使用
- 撮影した動画の編集
- 本問題の意図に沿わない回答
- 公序良俗に反する回答


![問題１.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3593537/6ca59969-229a-74e3-f01f-f79421789f5c.png)


### むずかしい？
無駄に凝ったので一筋縄では解けないかもしれません。
クリーパーがいなければ「**MISECOMA**」の文字が作れそうですが……これは禁止されていますね。
想定解は下の方に書いておきます。

# どんな記事？
先ほど見ていただいた、おびただしい数の禁止事項。
今回の例はかなりふざけていますが、これが脱出マップならば禁止事項を犯すわけにはいきませんよね。
禁止事項の数が多いほど、プレイヤーは常にそのことを念頭に置いてプレイしなければならないため、純粋に楽しむことが出来なくなってしまいます。
そこで「禁止」とプレイヤーへ委ねるようなことは極力減らし、コマンドを使って**マップ自体**で「不可能」を作ろう！という記事となります。

# クラフトの禁止（JavaEdition向け）
### マップの設定
JavaEditionでは```/reciepe```コマンドを使用してプレイヤーのアイテムクラフトを不可能にすることができます。
攻略に必要なアイテムをクラフトされないように、コマンドで予め設定しておきましょう。
```
# クラフトの際にレシピを必要とするようにゲームルールを変更
gamerule doLimitedCrafting true
```
このゲームルールを有効にすることで、プレイヤーはクラフトする際にレシピが必要となります。
```
# 常時実行；すべてのアイテム(= * )のクラフトを禁止
recipe take @a *
```
```/reciepe take```コマンドを使用することで、プレイヤーのレシピをすべてはく奪することができます。
プレイヤーがアイテムを取得するとレシピが更新されてしまうため、このコマンドは常時実行にしておきます。
これで、プレイヤーはすべてのアイテムのクラフトをすることができなくなりました。

### 一部は可能にする
特定のアイテムはクラフト可能にしたい場合は```/reciepe give```でレシピを与えましょう。
```
# minecraft:iron_chestplateのクラフトを許可
recipe give @a minecraft:iron_chestplate
```

### BEは？
統合版には```/reciepe```コマンドが無いため、すべてのアイテムのクラフトを不可能にすることはできません。
しかしながら、元々クラフト素材にならないアイテムを使用することでクラフトを防ぐことはできます。

# コマンドの使用禁止
多くの脱出マップのプレイヤーは、Minecraft Command Advent Calendarをご覧になっている皆様ほどコマンドについて詳しいわけではありません。
ここでは、すべてのコマンドの使用禁止ではなく、以下の三つのコマンドに絞って対策方法を書いていきます。
- ```/gamemode```
- ```/tp```
- ```/kill```

### ```/gamemode```の禁止
ゲームモードの変更を不可能にするためには単純明快、以下のコマンドを常時実行にします。
```
# 常時実行；ゲームモードをadventureに固定
gamemode adventure @a[gamemode=!adventure]
```

### ```/tp```の禁止
まず下準備として、ワールド内のプレイヤーにスコアボードで個別のIDを割り当てます。
```
# スコアボードを用意
scoreboard objectives add ID dummy
scoreboard players set #ID ID 0
```
```
# 常時実行；プレイヤーにIDを割り当てる
execute as @a unless score @s ID matches 0.. run function test:id_set
```
test:id_set の中身↓
```
# ダミープレイヤー#IDのIDを1加算させて、プレイヤーにその値を付与
scoreboard players add #ID ID 1
scoreboard players operation @s ID = #ID ID
```
これですべてのプレイヤーがスコアボードで識別できるようになりました。

:::note
tpを使用したかどうかは、プレイヤーがtp前にいた座標とかけ離れた場所にいるという条件で判定できます。
:::
これを実現するために、常にプレイヤーの座標にmarkerを追従させておいて、そのmarkerと一定距離はなれた場合、元の座標へ戻します。

test:id_set にプレイヤーの座標を追うmarkerを召喚するコマンドを追記します↓
```
# ダミープレイヤー#IDのIDを1加算させて、プレイヤーにその値を付与
scoreboard players add #ID ID 1
scoreboard players operation @s ID = #ID ID

# 追従用のmarkerを召喚して、プレイヤーと同じIDを設定する
execute at @s run summon marker ~ ~ ~ {Tags:["not_tp","not_tp_temp"]}
scoreboard players operation @e[tag=not_tp_temp] ID = @s ID
tag @e[tag=not_tp_temp] remove not_tp_temp
```

IDを割り当てている常時実行function内で、tpを禁止するfunctionも常時実行させます↓
```
# 常時実行；プレイヤーにIDを割り当てる
execute as @a unless score @s ID matches 0.. run function test:id_set
# 常時実行；tpの禁止
execute as @e[tag=not_tp] at @s run function test:not_tp
```
test:not_tp の中身↓
```
# tpの禁止
scoreboard players operation @a ID -= @s ID
execute as @a[scores={ID=0},distance=5..,tag=!can_tp] run tellraw @s {"text": "tpは禁止されています！"}
tp @a[scores={ID=0},distance=5..,tag=!can_tp] @s
tp @s @a[scores={ID=0},limit=1]
scoreboard players operation @a ID += @s ID
```

::: note warn
**注意点**
:::
上記コマンドを用いると、ギミックによるtpやエンダーパールによる移動も不可能となります。
ギミックによるtpを行いたい場合は以下のコマンドでタグを付与し、tpを一時的に許可します。

```
# tp許可タグを付与
tag @s add can_tp
```

### ```/kill```の禁止
実は、上の```/tp```禁止のコマンドでその場リスポーンも兼ね備えています。
以下のゲームルールを変更してアイテムを失わないようにしたり、即時リスポーンを有効にすることで```/kill```で実質的に何も起こらなかったように演出できます。
```
# 死亡時インベントリ保存の有効化
gamerule keepInventory true
# 即時リスポーンの有効化
gamerule doImmediateRespawn true
```

### コマンド不可能化の注意点
脱出マップを遊ぶプレイヤーがコマンドを使用する場合というのは、基本的に何らかの理由で詰んだ状況が大半です。
詰んだ状況を打開できうるコマンドを封じてしまうのは、本当の意味で詰んでしまいワールドの入れ直しを強要する可能性もあります。
これらのコマンド不可能化のシステムを搭載するのは、**一切の詰み要素を排除した強固で頑丈な脱出マップ**のみにしましょう。

# アイテムの紛失禁止
重要なアイテムが放置で消滅した場合、詰んでしまう可能性があります。
コマンドで五分経っても消滅しないように設定しておきます。
なお、```/give```でアイテムが出現した瞬間にデータを変更するとプレイヤーからゴーストアイテムが射出されるため、1tick遅らせて設定することで対策をしています。
```
# 常時実行；アイテムの設定を行う
execute as @e[type=item,tag=!item_set,tag=item_setup] run function test:item
# /giveでのバグ回避のために1tick遅らせるタグを付与
execute as @e[type=item,tag=!item_set] run tag @s add item_setup
```
test:itemの中身↓
```
# 設定済みであるタグを付与
tag @s add item_set
# アイテムの消滅時間を無限に設定
data modify entity @s Age set value -32768
# アイテムを無敵にする
data modify entity @s Invulnerable set value true
```
::: note warn
**注意点**

上記コマンドを用いると、ドロップアイテムはスタックしなくなります。
:::


# ワールド設定
禁止事項とは少し離れますが、プレイヤーがワールドに初めて入ってきた時に色々処理をしておくとサーバーや環境による違いを緩和できます。

### プレイヤー設定
上で使用したプレイヤーにIDを割り振る処理のところで、他の設定も行っておきます↓
```
# 常時実行；プレイヤーにIDを割り当てる
execute as @a unless score @s ID matches 0.. run function test:id_set
```
test:id_set の中身↓
```
#(ID付与等は省略)

# ゲームモードをadventureに設定
gamemode adventure @s

# 脱出マップの初期地点(ここでは10 20 30)へ移動
tp @s 10 20 30

# アイテムを消去
clear @s

# エフェクトを消去
effect clear @s

# リスポーンポイントを初期地点と同じ座標に設定
spawnpoint @s 10 20 30

# 経験値を消去
experience set @s 0 levels
experience set @s 0 points

# レシピを消去
recipe take @s *

# 進捗を消去
advancement revoke @s everything
```
### ゲームルール設定
また同様に、ワールドのゲームルール等の設定もこの中に入れておきます。
test:id_set の中身↓
```
#(諸々省略)

# 難易度をピースフルに設定
difficulty peaceful

# 時間の経過を停止
time set 0
gamerule doDaylightCycle false

# 炎の延焼を無効化
gamerule doFireTick false

# 落下ダメージを無効化
gamerule fallDamage false

# 死亡時インベントリ保存の有効化
gamerule keepInventory true
```

# サーバー設定
「禁止事項の無い」というよりも、**全体的に注意事項の少ない脱出マップ**を目標とします。
脱出マップをマルチプレイで遊ぶ際、シングルプレイとは異なる設定が施されます。
server.propertiesのデフォルト設定がなかなか厄介で、設定次第ではギミックが一切作動しなくなるものもあります。
脱出マップに関係する設定と、その対処法について書いていきます。
### gamemode=survival
対処法：上に書いた、プレイヤーが初めてワールドに入ってきたときの処理でゲームモードを上書きする。
### difficulty=easy
対処法：ゲームモードと同様、難易度をコマンドで設定し直す。
### pvp=true
プレイヤーが他のプレイヤーを殴り倒してしまう可能性があります。
ゲームモードがピースフルでも回復量が間に合わず倒れてしまいます。
対処法：弱体化のエフェクトを与えて他のエンティティを殴れないようにする。
```
effect give @s minecraft:weakness infinite 100 true
```
### enable-command-block=true
コマンドブロックの動作を禁止する設定です。
だいたいこいつのせいです。
対処法：**すべてのコマンドをfunctionで記述する。**

コマンドブロックが使えないため、ボタンを押した等の処理はボタンの状態を常に監視しておきます↓
```
# 10 20 30にある石のボタンが押されているかどうかを監視
execute if block 10 20 30 minecraft:stone_button[powered=true] run say ボタンが押されている！
```
ただし、これだとボタンが押されている間ずっとコマンドが実行されるため、押された瞬間の一回のみの処理を行いたい場合はフラグ処理を挟みます。

まず、フラグ管理用のスコアボードを用意します↓
```
# フラグ管理用のスコアボードを設定
scoreboard objectives add Button dummy
scoreboard players set #Button_1 Button 0
scoreboard players set #Button_2 Button 0
scoreboard players set #Button_3 Button 0
```
特定のボタンが押された時の処理は以下のように実装します↓
```
# フラグが0なら、押された瞬間の処理を行う
execute if score #Button_1 Button matches 0 if block 10 20 30 minecraft:stone_button[powered=true] run function test:button_1
# フラグが1でボタンが元に戻っているなら、フラグを0に戻す
execute if score #Button_1 Button matches 1 if block 10 20 30 minecraft:stone_button[powered=false] run scoreboard players set #Button_1 Button 0
```
test:button_1の中身↓
```
say ボタンが押された！
scoreboard players set #Button_1 Button 1
```

### spawn-protection=16
OP権限を持たないプレイヤーの、ワールド原点から16ブロック以内のブロック破壊やボタン操作を禁止する設定です。
対処法１：ワールド原点から遠く離れたところにギミックを配置する。
対処法２：OP権限の付与をお願いする。
対処法３：ボタン・レバーを使わない。

# レバーの連打禁止
レッドストーントーチを使っていると、レバーの連打によって焼き切れて再起不能となっていまう場合があります。
これはコマンドブロックを使わずに、上で行ったようにすべてのコマンドをfunctionで実行することで連打に対応できます。

# 描画距離・演算距離
ブロック操作を行うチャンクが読み込まれていないと、正常にコマンドが動作しないという理由で設定の推奨をしている場合は、```/forceload```コマンドでそのチャンクを読み込んでおくことで対処可能です。
```
# 自分が今いるチャンクを強制読み込みチャンクに追加
forceload add ~ ~
# x=10,z=10を含むチャンクを強制読み込みチャンクに追加
forceload add 10 10
```

遠景や演出を見せたいときは描画距離の設定を記載しておきましょう。


# OP権限クリック
### チャットのクリック
```/tellraw```コマンドや本の文字のクリックでコマンドを実行するには、通常OP権限が必要です。
こういった場合は、```/trigger```コマンドを用いることでOP権限がなくともコマンドを実行することができます。

以下の手順でチャットのクリックを実現します↓
```
# trigger用のスコアボードを設定
scoreboard objectives add click trigger
```
ワールドに初めて入ってきたプレイヤーの設定をする部分で、トリガーの有効化もしておきます。
test:id_set の中身↓
```
#(諸々省略)
# トリガーを有効化
scoreboard players enable @s click
# スコアボードを初期化
scoreboard players set @s click 0
```

```/tellraw```コマンドを使用して、クリック可能なチャットを流します↓
```
tellraw @a {"text":"ここをクリックしてね！","clickEvent":{"action":"run_command","value":"/trigger click set 1"}}
```

プレイヤーがこの文章をクリックしたときスコアボード```click```の値が更新されるので、常時実行コマンドで処理を行います↓
```
# チャットをクリックしたときの処理
execute as @a if score @s click matches 1 run say ナイスクリック！
# トリガーをリセット
execute as @a unless score @s click matches 0 run scoreboard players enable @s click
execute as @a unless score @s click matches 0 run scoreboard players set @s click 0
```


### 注意点

トリガーを有効にすると、プレイヤーはいつでも```/trigger click set 1```とチャットから打ち込むことでスコアボードの値を操作できます。
対策として、処理を行うスコアボードの値をパスワードのように長くしておくとよいでしょう。
```
tellraw @a {"text":"ここをクリックしてね！","clickEvent":{"action":"run_command","value":"/trigger click set 3141592"}}
```
```
# チャットをクリックしたときの処理
execute as @a if score @s click matches 3141592 run say ナイスクリック！
# トリガーをリセット
execute as @a unless score @s click matches 0 run scoreboard players enable @s click
execute as @a unless score @s click matches 0 run scoreboard players set @s click 0
```
### 本の入手
OP権限の必要のないクリックできる本は以下のコマンドで入手できます。
```
/give @p written_book{pages:['{"text":"クリックしてね","clickEvent":{"action":"run_command","value":"/trigger click set 3141592"}}'],title:Book,author:"Minecraft Command Advent Calendar 2023"}
```
### 通知の非表示
クリックした際に流れる```[click]をトリガーしました（ 値を3141592に設定しました ）```が気になる場合は、以下のコマンドで消去できます。
```
# コマンドの実行結果通知を無効化
gamerule sendCommandFeedback false
```

# 終わりに
ここに書かれているものはあくまでプレイヤーがより良い体験をすることができるようになるプラスアルファの事に過ぎず、**必須ではありません。**
禁止事項が明言されていたほうが安心するプレイヤーもいます。
自分のマップとの兼ね合いやバランスを考慮した上で、こういったシステムを取り入れてみてはいかがでしょうか！

# こたえ！

![問題１こたえ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3593537/1ca5fdaf-6068-058b-2d1c-45f310644f00.png)

ということで、想定解は「**MINECRAFT**」です！
壁をアルファベットに見立てるのはずるいって？
はて、「壁をアルファベットに見立てること」は禁止事項にありましたかね。