Minecraft Skriptでテレポータープラグインを作成する

Pocket
LINEで送る

はじめに

今年のゴールデンウィークに子供と一緒にMinecraftを楽しむため、自宅でPaperMCサーバーを立てました。その時に作ったコードの紹介をします。
ゲーム内で迷子になりやすい私は、テレポート機能を持つプラグインを探しましたが、気に入ったものが見つかりませんでした。そこで、Minecraft用の言語であるSkriptを使って自作することにしました。
Skriptの資料が少なく苦労しましたが、同じような問題を抱える方の参考になればと思い、私のスクリプトを公開します。皆様のお役に立てれば幸いです。
どうぞご利用ください!

対象

  • サーバー、及びプラグインは自力で設定できる方
  • Skriptをこれから勉強したい方

環境

  • サーバー:
    • marctv/minecraft-papermc-server:1.20.6 → Dockerで立ち上げました。
  • 使用プラグイン:
    • Skript-2.8.7.jar
    • SkCheese-1.4.1.jar
    • skript-yaml-1.5.jar
    • skUtilities.v0.9.2.jar

機能一覧

  • テレポート位置の登録・削除
  • テレポート位置のアイコンを選択可
  • 死亡時の位置を自動で登録
  • 登録位置にテレポート

使用方法

実行コマンド/tpでguiを開く

新規追加をクリック

名前を登録

アイコンを選択

追加完了

青ブロックをクリックして赤くなると削除モードになります

 

コードで参考になるかなと思う箇所

  • 定数 (options:)
  • 変数 (hoge:)
  • 配列 (hage::)
  • If文、Loop文、Case文
  • 引数を使った関数 (引数がhogeの場合関数内は_hogeとして扱う。_をつけるとローカル変数になる)
  • Skriptでのテキスト入力方法(テレポート新規作成時に名前を付ける部分で使っています
  • yamlの読み書き
  • configファイルの設定
  • guiの表示及びページング

文法について

ほぼ英語の文法そのままで記載できます

  • if event-item is stone named ”reload” → event-itemは名前がreloadのstoneか?
  • set {a} to “b” → 変数aにbをセット
  • set {_lc} to location of {_p} → 変数_lcに変数_pのlocation をセット

コード

もっとすっきり書けるかもしれませんがこれが限界です。

/plugins/Skript/data/config.yml

debug: false
page_size: 36 #max 36 1ページ内に表示する項目数

/plugins/Skript/mytp.sk

options:

    yml_dir:"plugins/Skript/data/"

    answer_y:"y"
    answer_n:"n"
    answer_q:"q"
    answer_any:"any"

    act_add:"act_add"
    act_del:"act_del"
    act_close:"act_close"
    act_select_icon:"act_select_icon"
    act_teleport:"act_teleport"
    act_next:"act_next"
    act_prev:"act_prev"

    gui_name1:"TP MENU"
    gui_name2:"SELECT ITEM"

    key_yml_id:"key_yml_id"
    key_click_item_id:"click_id"

    key_mode:"key_mode"
    value_mode_add:"value_mode_add"
    value_mode_delete:"value_mode_delete"

    key_input_status:"key_input_status"
    value_input_status_none:"value_input_status_none"
    value_input_status_wait:"value_input_status_wait"
    value_input_status_inputed:"value_input_status_inputed"

    key_input_name:"key_input_name"
    key_is_tp_close:"key_is_tp_close"
    key_is_tp_start:"key_is_tp_start"

    key_tp_list:"key_tp_list"
    key_tp_is_delete:"key_tp_is_delete"
    key_tp_max_page:"key_tp_max_page"
    key_tp_page:"key_tp_page"
    key_tp_count:"key_tp_count"
    key_lc_location:"key_lc_location"
    key_lc_name:"key_lc_name"
    key_lc_item:"key_lc_item"
    key_lc_action:"key_lc_action"
    key_lc_select_no:"key_lc_select_no"

    key_gui_name1_slots:"key_gui_name1_slots"

    pink:pink stained glass
    red:red stained glass
    blue:blue stained glass
    yellow:yellow stained glass

local function clearIcons():
    clear {ICONS::*}

local function setIcons(myslot:object,myitem:object,myname:object,myaction:object):
    set {ICONS::%{_myslot}%} to {_myslot}
    set {ICONS::%{_myslot}%::item} to {_myitem}
    set {ICONS::%{_myslot}%::name} to {_myname}
    set {ICONS::%{_myslot}%::action} to {_myaction}

on load:

    clearIcons()
    setIcons(0,{@pink},"ピンク",{@act_select_icon})
    setIcons(1,{@red},"赤",{@act_select_icon})
    setIcons(2,{@blue},"青",{@act_select_icon})
    setIcons(3,{@yellow},"黄",{@act_select_icon})
    setIcons(53,barrier,"止める",{@act_close})

    load yaml "%{@yml_dir}%config.yml" as "config"
    set {DEBUG} to yaml value "debug" from "config"
    set {PAGE_SIZE} to yaml value "page_size" from "config"

command /tp:
    trigger:
        openGui(player)

local function setSlots(p:player,slot:number,item:object,name:object,action:object):
    setProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%",{_slot})
    setProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%::name",{_name})
    setProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%::item",{_item})
    setProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%::action",{_action})


local function openGui(p:player):
    setProp({_p},{@key_is_tp_start},true)
    getLocation({_p})

    deleteListProp({_p},{@key_gui_name1_slots})

    set {_del} to getProp({_p},{@key_tp_is_delete})
    if {_del} is true:
        setSlots({_p},46,red concrete,"削除中止",{@act_del})
    else:
        setSlots({_p},46,blue concrete,"削除",{@act_del})

    setSlots({_p},45,white stained glass,"新規追加",{@act_add})
   
    setSlots({_p},53,barrier,"閉じる",{@act_close})

    set {_page} to getProp({_p},{@key_tp_page}) + 1
    set {_max_page} to getProp({_p},{@key_tp_max_page})
    if 1 < {_page}:
        setSlots({_p},49,stone,"前ページ",{@act_prev})

    if {_page} < {_max_page}:
        setSlots({_p},51,stone,"次ページ",{@act_next})

    setSlots({_p},50,white stained glass pane,"%{_page}%/%{_max_page}%","")

    loop {PAGE_SIZE} times:
        set {_lc_num} to ({_page} -1 ) * {PAGE_SIZE} + loop-number - 1
        debugLog({_p},"load lc no :%{_lc_num}%  page:%{_page}%  count:%{PAGE_SIZE}%")
        set {_name} to getProp({_p},"%{@key_tp_list}%::%{_lc_num}%::%{@key_lc_name}%")
        set {_item} to getProp({_p},"%{@key_tp_list}%::%{_lc_num}%::%{@key_lc_item}%")
        set {_action} to getProp({_p},"%{@key_tp_list}%::%{_lc_num}%::%{@key_lc_action}%")
        setSlots({_p},loop-number - 1,{_item},{_name},{_action})

    open chest inventory with 6 row named {@gui_name1} to {_p}

    loop getListProp({_p},{@key_gui_name1_slots}):
        set {_slot} to loop-value
        debugLog({_p},"getListProp %{_slot}%")
        set {_name} to getProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%::name")
        set {_item} to getProp({_p},"%{@key_gui_name1_slots}%::%{_slot}%::item")
        set slot {_slot} of {_p}'s current inventory to {_item} named {_name}

        if {DEBUG} is true:
            set slot 47 of {_p}'s current inventory to stone named "reload"



local function openGui2(p:player):
    open chest inventory with 6 row named {@gui_name2} to {_p}

    loop {ICONS::*}:
        set {_slot} to loop-index
        set slot {ICONS::%{_slot}%} of {_p}'s current inventory to {ICONS::%{_slot}%::item} named {ICONS::%{_slot}%::name}
        

on inventory click:

    set {_slot} to index of event-slot

    name of clicked inventory is {@gui_name1}:
        cancel event

        if event-item is stone named "reload":
            debugLog(player,"reload")
            execute player command "/skript reload scripts"
            close player's inventory

        set {_action} to getProp(player,"%{@key_gui_name1_slots}%::%{_slot}%::action")

        debugLog(player,"_action :%{_action}%")
        switch {_action}:
            case {@act_add}:
                send "名前を入力してください  (q:キャンセル)" to player
                setProp(player,{@key_input_status},{@value_input_status_wait})
                closeForNext(player)
            case {@act_teleport}:
                set {_lc_no} to {_slot} + {PAGE_SIZE} * getProp(player,{@key_tp_page})
                
                if {_lc_no} is getProp(player,{@key_lc_select_no}):
                    set {_del} to getProp(player,{@key_tp_is_delete})
                    if {_del} is true:
                        set {_lc_id} to getProp(player,"%{@key_tp_list}%::%{_lc_no}%")
                        debugLog(player,"del lc id:%{_lc_id}%")
                        delLocation(player,{_lc_id})
                        exit(player)
                        openGui(player)
                    else:
                        set {_lc} to getProp(player,"%{@key_tp_list}%::%{_lc_no}%::%{@key_lc_location}%")
                        teleport player to {_lc}
                        exit(player)
                else:
                    setProp(player,{@key_lc_select_no},{_lc_no})

            case {@act_close}:
                exit(player)
            case {@act_next}:
                set {_page} to getProp(player,{@key_tp_page}) + 1
                setProp(player,{@key_tp_page},{_page})
                closeForNext(player)
                openGui(player)
            case {@act_prev}:
                set {_page} to getProp(player,{@key_tp_page}) - 1
                setProp(player,{@key_tp_page},{_page})
                closeForNext(player)
                openGui(player)
            case {@act_del}:
                setProp(player,{@key_lc_select_no},-1)
                set {_del} to getProp(player,{@key_tp_is_delete})
                if {_del} is true:
                    setProp(player,{@key_tp_is_delete},false)
                else:
                    setProp(player,{@key_tp_is_delete},true)
                closeForNext(player)
                openGui(player)
            default:
                set {_none} to true

    name of clicked inventory is {@gui_name2}:
        cancel event

        switch {ICONS::%{_slot}%::action}:
            case {@act_select_icon}:
                set {_item} to {ICONS::%{_slot}%::item}
                set {_name} to getProp(player,{@key_input_name})
                saveLocation(player,{_item},{_name})
                exit(player)
                openGui(player)
            case {@act_close}:
                clearPlayerStatus(player)
                closeForNext(player)
                openGui(player)
            default:
                set {_none} to true

local function exit(p:player):
    close {_p}'s inventory

local function closeForNext(p:player):
    setProp({_p},{@key_is_tp_close},true)
    close {_p}'s inventory
            
on inventory close:
    if getProp(player,{@key_is_tp_close}) is true:
        setProp(player,{@key_is_tp_close},false)
        debugLog(player,"イベントクローズ")
    else:
        debugLog(player,"その他クローズ")
        if getProp(player,{@key_is_tp_start}) is true:
            clearPlayerStatus(player)

on death of player:
    if victim is player:
        saveLocation(victim,stone,"死んだ!")

local function saveLocation(p:player,myitem:object,myname:object):
    set {_lc_uuid} to random uuid
    set {_lc} to location of {_p}
    set {_yml_id} to getProp({_p},{@key_yml_id})
    set yaml value "locations.%{_lc_uuid}%.lc" from {_yml_id} to {_lc}
    set yaml value "locations.%{_lc_uuid}%.name" from {_yml_id} to {_myname}
    set yaml value "locations.%{_lc_uuid}%.item" from {_yml_id} to {_myitem}
    save yaml {_yml_id}
    
local function getLocation(p:player):
    deleteListProp({_p},{@key_tp_list})
    set {_yml_id} to getProp({_p},{@key_yml_id})
    set {_cnt} to 0
    loop yaml node list "locations" from {_yml_id}:
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%",loop-value-1)
        set {_lc} to yaml value "%loop-value-1%.lc" from {_yml_id}
        debugLog({_p},"loc x %x of {_lc}%")
        set {_name} to yaml value "%loop-value-1%.name" from {_yml_id}
        set {_item} to yaml value "%loop-value-1%.item" from {_yml_id}
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%",loop-value-1)
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%::%{@key_lc_location}%",{_lc})
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%::%{@key_lc_name}%",{_name})
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%::%{@key_lc_item}%",{_item})
        setProp({_p},"%{@key_tp_list}%::%{_cnt}%::%{@key_lc_action}%",{@act_teleport})
        add 1 to {_cnt}
    setProp({_p},{@key_tp_count},{_cnt})
    setProp({_p},{@key_tp_max_page},ceil({_cnt} / {PAGE_SIZE}))

local function delLocation(p:player,lc_id:text):
    set {_yml_id} to getProp({_p},{@key_yml_id})
    delete yml value {_lc_id} from {_yml_id}
    save yaml {_yml_id}

on chat:
    set {_input} to message
    if getProp(player,{@key_input_status}) is {@value_input_status_wait}:
        set {_ans} to answerCheck({_input})
        if {_ans} is {@answer_q}:
            send "登録をキャンセルしました" to player
            clearPlayerStatus(player)
        else:
            setProp(player,{@key_input_status},{@value_input_status_inputed})
            setProp(player,{@key_input_name},{_input})
            openGui2(player)
        cancel event
    
command /tpinit:
    trigger:
        playerInit(player)
        
on join:
    playerInit(player)

on quit:
    playerQuit(player)


local function playerInit(p:player):
    set {_name} to name of {_p}
    set {_yml_id} to "%{@yml_dir}%%{_name}%.yml"
    load yaml {_yml_id} as {_yml_id}

    clear {prop::%{_uuid}%::*}
    setProp({_p},{@key_yml_id},{_yml_id})
    clearPlayerStatus({_p})

    debugLog({_p},"use debugLog")

local function clearPlayerStatus(p:player):
    setProp({_p},{@key_input_status},{@value_input_status_none})
    setProp({_p},{@key_input_name},"")
    setProp({_p},{@key_is_tp_close},false)
    setProp({_p},{@key_is_tp_start},false)
    setProp({_p},{@key_tp_max_page},0)
    setProp({_p},{@key_tp_count},0)
    setProp({_p},{@key_tp_page},0)
    setProp({_p},{@key_tp_is_delete},false)
    setProp({_p},{@key_lc_select_no},-1)

local function playerQuit(p:player):
    set {_uuid} to uuid of {_p}
    delete {prop::%{_uuid}%::*}
    set {_yml_id} to getProp({_p},{@key_yml_id})
    unload yaml {_yml_id}

local function setProp(p:player,key:object,val:object):
    debugLog({_p}, "setProp %{_key}% %{_val}%" )
    set {_uuid} to uuid of {_p}
    set {prop::%{_uuid}%::%{_key}%} to {_val}

local function getProp(p:player,key:string)::object:
    set {_uuid} to uuid of {_p}
    return {prop::%{_uuid}%::%{_key}%}

local function getListProp(p:player,key:string)::objects:
    set {_uuid} to uuid of {_p}
    return {prop::%{_uuid}%::%{_key}%::*}

local function deleteListProp(p:player,key:string):
    set {_uuid} to uuid of {_p}
    delete {prop::%{_uuid}%::%{_key}%::*}
    
local function answerCheck(inp: string) :: string:
    switch {_inp} in lowercase:
        case {@answer_y}:
            return {@answer_y}
        case {@answer_n}:
            return {@answer_n}
        case {@answer_q}:
            return {@answer_q}
        default:
            return {@answer_any}

command /tppropcheck:
    trigger:
        set {_uuid} to uuid of player
        loop {prop::%{_uuid}%::*}:
            send "prop %loop-index% : %loop-value%" to player

local function debugLog(p:player,mes:text):
    if {DEBUG} is true:
        send "--debug %{_mes}%" to {_p}

終わりに

  • 英語の文法で書くというところが慣れないと難しい
  • 簡単に書けるようになっている分javaなどに比べ機能が削られているのでそれはそれで難しい
  • 本格的なプラグインはjavaで書く必要があるため、気軽に作れるのはいいところ
Pocket
LINEで送る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください