From af884706d4663aca03f43f29b4f85c42bb867117 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Wed, 13 May 2026 16:34:34 +0900 Subject: [PATCH] =?UTF-8?q?op:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=ED=8C=A9=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=EC=9D=84=20=EC=8B=A4=EC=A0=9C=20music=5Fquiz?= =?UTF-8?q?=20zip=20=EC=9C=BC=EB=A1=9C=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가이드 (mc_datapack/launcher_datapack_연동_가이드.txt) 에 따라: - file/datapacks/music_quiz_template/ 에 mc_datapack 의 music_quiz/ 정적 파일을 미리 동봉 (data/mq/function/init/songs.mcfunction 제외). - src/server/datapack.ts: list.music → SNBT (`{title, author, alias}`) songs.mcfunction 빌더와 archiver 기반 zip 스트리머 추가. - /op/datapack/:packName/generate 가 텍스트 placeholder 대신 music_quiz_.zip 을 Content-Disposition attachment 로 내려준다. - datapack.ejs 의 코드블록·복사 UI 제거, 곡 수는 서버 렌더 시점에 표시. - 더 이상 쓰이지 않는 locales 의 datapackOutput.* 키 제거, datapack 버튼 라벨/상태 문구를 zip 다운로드용으로 정리. --- .../data/func/function/comp_num.mcfunction | 7 ++ .../data/func/function/half.mcfunction | 15 ++++ .../data/func/function/half/f1.mcfunction | 1 + .../data/func/function/half/f2.mcfunction | 5 ++ .../data/func/function/hint.mcfunction | 18 +++++ .../data/func/function/is_index.mcfunction | 2 + .../data/func/function/is_space.mcfunction | 2 + .../data/func/function/join.mcfunction | 4 + .../data/func/function/join/f1.mcfunction | 8 ++ .../data/func/function/join/f2.mcfunction | 5 ++ .../data/func/function/length.mcfunction | 5 ++ .../data/func/function/num_list.mcfunction | 13 +++ .../data/func/function/num_list/f1.mcfunction | 12 +++ .../data/func/function/plus.mcfunction | 7 ++ .../data/func/function/shuffle.mcfunction | 9 +++ .../data/func/function/shuffle/f1.mcfunction | 11 +++ .../data/func/function/shuffle/f2.mcfunction | 4 + .../data/func/function/text_list.mcfunction | 13 +++ .../func/function/text_list/f1.mcfunction | 16 ++++ .../data/minecraft/tags/function/load.json | 5 ++ .../data/minecraft/tags/function/tick.json | 5 ++ .../data/mq/advancement/player/login.json | 23 ++++++ .../data/mq/dialog/page1.json | 46 +++++++++++ .../data/mq/dialog/page2.json | 45 +++++++++++ .../data/mq/dialog/page3.json | 46 +++++++++++ .../data/mq/function/commands/hint.mcfunction | 14 ++++ .../mq/function/commands/replay.mcfunction | 9 +++ .../data/mq/function/commands/skip.mcfunction | 9 +++ .../mq/function/commands/start.mcfunction | 10 +++ .../data/mq/function/commands/stop.mcfunction | 59 ++++++++++++++ .../data/mq/function/commands/test.mcfunction | 5 ++ .../data/mq/function/images/clear.mcfunction | 1 + .../mq/function/images/macro/show.mcfunction | 1 + .../data/mq/function/images/show.mcfunction | 3 + .../data/mq/function/init/buttons.mcfunction | 7 ++ .../data/mq/function/init/config.mcfunction | 23 ++++++ .../data/mq/function/init/triggers.mcfunction | 5 ++ .../data/mq/function/load.mcfunction | 29 +++++++ .../data/mq/function/players/login.mcfunction | 10 +++ .../data/mq/function/quiz/correct.mcfunction | 32 ++++++++ .../data/mq/function/quiz/end.mcfunction | 3 + .../function/quiz/macro/play_sound.mcfunction | 1 + .../function/quiz/macro/setanswer.mcfunction | 3 + .../function/quiz/macro/stop_sound.mcfunction | 1 + .../mq/function/quiz/macro/summon.mcfunction | 9 +++ .../mq/function/quiz/macro/summon2.mcfunction | 6 ++ .../mq/function/quiz/play_sound.mcfunction | 5 ++ .../data/mq/function/quiz/select.mcfunction | 20 +++++ .../mq/function/quiz/setanswer.mcfunction | 17 ++++ .../data/mq/function/quiz/start.mcfunction | 6 ++ .../mq/function/quiz/stop_sound.mcfunction | 1 + .../mq/function/repeat/buttons/btn.mcfunction | 28 +++++++ .../repeat/buttons/handler.mcfunction | 6 ++ .../function/repeat/check_answer.mcfunction | 2 + .../mq/function/repeat/players.mcfunction | 2 + .../data/mq/function/repeat/timer.mcfunction | 49 ++++++++++++ .../repeat/triggers/handler.mcfunction | 24 ++++++ .../repeat/triggers/trigger.mcfunction | 27 +++++++ .../data/mq/function/tellraw.mcfunction | 1 + .../data/mq/function/tick.mcfunction | 6 ++ .../datapacks/music_quiz_template/pack.mcmeta | 6 ++ locales/server/ko-kr.json | 17 +--- src/server/datapack.ts | 79 +++++++++++++++++++ src/server/routes/op.ts | 33 +++----- src/shared/paths.ts | 1 + views/op/datapack.ejs | 46 +++-------- 66 files changed, 871 insertions(+), 72 deletions(-) create mode 100644 file/datapacks/music_quiz_template/data/func/function/comp_num.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/half.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/half/f1.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/half/f2.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/hint.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/is_index.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/is_space.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/join.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/join/f1.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/join/f2.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/length.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/num_list.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/num_list/f1.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/plus.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/shuffle.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/shuffle/f1.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/shuffle/f2.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/text_list.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/func/function/text_list/f1.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/minecraft/tags/function/load.json create mode 100644 file/datapacks/music_quiz_template/data/minecraft/tags/function/tick.json create mode 100644 file/datapacks/music_quiz_template/data/mq/advancement/player/login.json create mode 100644 file/datapacks/music_quiz_template/data/mq/dialog/page1.json create mode 100644 file/datapacks/music_quiz_template/data/mq/dialog/page2.json create mode 100644 file/datapacks/music_quiz_template/data/mq/dialog/page3.json create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/hint.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/replay.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/skip.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/start.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/stop.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/commands/test.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/images/clear.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/images/macro/show.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/images/show.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/init/buttons.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/init/config.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/init/triggers.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/load.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/players/login.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/correct.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/end.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/macro/play_sound.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/macro/setanswer.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/macro/stop_sound.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon2.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/play_sound.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/select.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/setanswer.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/start.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/quiz/stop_sound.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/btn.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/handler.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/check_answer.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/players.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/timer.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/handler.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/trigger.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/tellraw.mcfunction create mode 100644 file/datapacks/music_quiz_template/data/mq/function/tick.mcfunction create mode 100644 file/datapacks/music_quiz_template/pack.mcmeta create mode 100644 src/server/datapack.ts diff --git a/file/datapacks/music_quiz_template/data/func/function/comp_num.mcfunction b/file/datapacks/music_quiz_template/data/func/function/comp_num.mcfunction new file mode 100644 index 0000000..c2f8e1f --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/comp_num.mcfunction @@ -0,0 +1,7 @@ +scoreboard objectives add func.temp dummy + +$execute store result score n1 func.temp run data get storage func:temp $(n1) +$execute store result score n2 func.temp run data get storage func:temp $(n2) + +execute if score n1 func.temp = n2 func.temp run return 1 +return 0 diff --git a/file/datapacks/music_quiz_template/data/func/function/half.mcfunction b/file/datapacks/music_quiz_template/data/func/function/half.mcfunction new file mode 100644 index 0000000..37c9966 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/half.mcfunction @@ -0,0 +1,15 @@ +scoreboard players set two func.temp 2 + +$data modify storage func:temp half.result set from storage func:temp $(list) + +execute store result score length func.temp run data get storage func:temp half.result + +scoreboard players operation half func.temp = length func.temp +scoreboard players operation half func.temp /= two func.temp + +scoreboard players operation odd func.temp = length func.temp +scoreboard players operation odd func.temp %= two func.temp + +scoreboard players operation half func.temp += odd func.temp + +function func:half/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/half/f1.mcfunction b/file/datapacks/music_quiz_template/data/func/function/half/f1.mcfunction new file mode 100644 index 0000000..b793cff --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/half/f1.mcfunction @@ -0,0 +1 @@ +execute if score half func.temp < length func.temp run function func:half/f2 diff --git a/file/datapacks/music_quiz_template/data/func/function/half/f2.mcfunction b/file/datapacks/music_quiz_template/data/func/function/half/f2.mcfunction new file mode 100644 index 0000000..770a4f5 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/half/f2.mcfunction @@ -0,0 +1,5 @@ +data remove storage func:temp half.result[-1] + +scoreboard players remove length func.temp 1 + +function func:half/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/hint.mcfunction b/file/datapacks/music_quiz_template/data/func/function/hint.mcfunction new file mode 100644 index 0000000..5bb7d9a --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/hint.mcfunction @@ -0,0 +1,18 @@ +$function func:length {text:"$(text)"} +# return length +function func:num_list with storage func:temp +# return num_list +function func:shuffle {list:"num_list"} +# return shuffle.result +function func:half {list:"shuffle.result"} +# return half.result + +$function func:length {text:"$(text)"} +# return length +function func:text_list with storage func:temp +# return text_list + +function func:join {list:"text_list"} +# return join.text + +# tellraw @a {"storage":"func:temp","nbt":"join.text"} diff --git a/file/datapacks/music_quiz_template/data/func/function/is_index.mcfunction b/file/datapacks/music_quiz_template/data/func/function/is_index.mcfunction new file mode 100644 index 0000000..704b74e --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/is_index.mcfunction @@ -0,0 +1,2 @@ +$execute if data storage func:temp {$(l1):{$(l2):[$(index)]}} run return 1 +return 0 diff --git a/file/datapacks/music_quiz_template/data/func/function/is_space.mcfunction b/file/datapacks/music_quiz_template/data/func/function/is_space.mcfunction new file mode 100644 index 0000000..5336585 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/is_space.mcfunction @@ -0,0 +1,2 @@ +$execute if data storage func:temp {space:{text:"$(space)"}} run return 1 +return 0 diff --git a/file/datapacks/music_quiz_template/data/func/function/join.mcfunction b/file/datapacks/music_quiz_template/data/func/function/join.mcfunction new file mode 100644 index 0000000..aa21eb3 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/join.mcfunction @@ -0,0 +1,4 @@ +data modify storage func:temp join.text set value "" +$data modify storage func:temp join.list set from storage func:temp $(list) + +function func:join/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/join/f1.mcfunction b/file/datapacks/music_quiz_template/data/func/function/join/f1.mcfunction new file mode 100644 index 0000000..349519d --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/join/f1.mcfunction @@ -0,0 +1,8 @@ +execute store result score length func.temp run data get storage func:temp join.list + +execute if score length func.temp matches 0 run return 1 + +data modify storage func:temp join.now set from storage func:temp join.text +data modify storage func:temp join.next set from storage func:temp join.list[0] + +function func:join/f2 with storage func:temp join diff --git a/file/datapacks/music_quiz_template/data/func/function/join/f2.mcfunction b/file/datapacks/music_quiz_template/data/func/function/join/f2.mcfunction new file mode 100644 index 0000000..b0fceae --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/join/f2.mcfunction @@ -0,0 +1,5 @@ +$data modify storage func:temp join.text set value "$(now)$(next)" + +data remove storage func:temp join.list[0] + +function func:join/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/length.mcfunction b/file/datapacks/music_quiz_template/data/func/function/length.mcfunction new file mode 100644 index 0000000..b6d9eb9 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/length.mcfunction @@ -0,0 +1,5 @@ +$data modify storage func:temp text set value "$(text)" + +execute store result storage func:temp length int 1 run data get storage func:temp text + +return run data get storage func:temp length diff --git a/file/datapacks/music_quiz_template/data/func/function/num_list.mcfunction b/file/datapacks/music_quiz_template/data/func/function/num_list.mcfunction new file mode 100644 index 0000000..55c5d9f --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/num_list.mcfunction @@ -0,0 +1,13 @@ +data modify storage func:temp space.space set value " " +data modify storage func:temp zero set value 0 +$data modify storage func:temp length set value $(length) + +execute store result score result func.temp run function func:comp_num {n1:"zero",n2:"length"} +execute if score result func.temp matches 1 run tellraw @s {"text":"length는 1이상 이어야 합니다.","color":"red"} +execute if score result func.temp matches 1 run return 0 + +data modify storage func:temp num_list set value [] +data modify storage func:temp index set value 0 +data modify storage func:temp index_next set value 1 + +function func:num_list/f1 with storage func:temp diff --git a/file/datapacks/music_quiz_template/data/func/function/num_list/f1.mcfunction b/file/datapacks/music_quiz_template/data/func/function/num_list/f1.mcfunction new file mode 100644 index 0000000..14dcb94 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/num_list/f1.mcfunction @@ -0,0 +1,12 @@ +$data modify storage func:temp space.text set string storage func:temp text $(index) $(index_next) +execute store result score result func.temp run function func:is_space with storage func:temp space + +$execute if score result func.temp matches 0 run data modify storage func:temp num_list append value $(index) + +function func:plus {name:"index",plus:1} +function func:plus {name:"index_next",plus:1} + +execute store result score result func.temp run function func:comp_num {n1:"index",n2:"length"} +execute if score result func.temp matches 1 run return 1 + +function func:num_list/f1 with storage func:temp diff --git a/file/datapacks/music_quiz_template/data/func/function/plus.mcfunction b/file/datapacks/music_quiz_template/data/func/function/plus.mcfunction new file mode 100644 index 0000000..a4db2cc --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/plus.mcfunction @@ -0,0 +1,7 @@ +scoreboard objectives add func.temp dummy + +$execute store result score temp func.temp run data get storage func:temp $(name) + +$scoreboard players add temp func.temp $(plus) + +$execute store result storage func:temp $(name) int 1 run scoreboard players get temp func.temp diff --git a/file/datapacks/music_quiz_template/data/func/function/shuffle.mcfunction b/file/datapacks/music_quiz_template/data/func/function/shuffle.mcfunction new file mode 100644 index 0000000..a38762a --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/shuffle.mcfunction @@ -0,0 +1,9 @@ +scoreboard objectives add func.temp dummy + +data modify storage func:temp shuffle.result set value [] + +$data modify storage func:temp shuffle.list set from storage func:temp $(list) + +execute store result score length func.temp run data get storage func:temp shuffle.list + +function func:shuffle/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/shuffle/f1.mcfunction b/file/datapacks/music_quiz_template/data/func/function/shuffle/f1.mcfunction new file mode 100644 index 0000000..0f2f0fe --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/shuffle/f1.mcfunction @@ -0,0 +1,11 @@ +execute store result score length func.temp run data get storage func:temp shuffle.list + +execute if score length func.temp matches 0 run return 1 + +execute store result score random func.temp run random value 0..2147483646 +scoreboard players operation random func.temp %= length func.temp + +execute run function func:shuffle/f2 with storage func:temp {index:0} + +execute store result storage func:temp shuffle.index int 1 run scoreboard players get random func.temp +function func:shuffle/f2 with storage func:temp shuffle diff --git a/file/datapacks/music_quiz_template/data/func/function/shuffle/f2.mcfunction b/file/datapacks/music_quiz_template/data/func/function/shuffle/f2.mcfunction new file mode 100644 index 0000000..d710a5c --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/shuffle/f2.mcfunction @@ -0,0 +1,4 @@ +$data modify storage func:temp shuffle.result append from storage func:temp shuffle.list[$(index)] +$data remove storage func:temp shuffle.list[$(index)] + +function func:shuffle/f1 diff --git a/file/datapacks/music_quiz_template/data/func/function/text_list.mcfunction b/file/datapacks/music_quiz_template/data/func/function/text_list.mcfunction new file mode 100644 index 0000000..1d21dcb --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/text_list.mcfunction @@ -0,0 +1,13 @@ +data modify storage func:temp space.space set value " " +data modify storage func:temp zero set value 0 +$data modify storage func:temp length set value $(length) + +execute store result score result func.temp run function func:comp_num {n1:"zero",n2:"length"} +execute if score result func.temp matches 1 run tellraw @s {"text":"length는 1이상 이어야 합니다.","color":"red"} +execute if score result func.temp matches 1 run return 0 + +data modify storage func:temp text_list set value [] +data modify storage func:temp index set value 0 +data modify storage func:temp index_next set value 1 + +function func:text_list/f1 with storage func:temp diff --git a/file/datapacks/music_quiz_template/data/func/function/text_list/f1.mcfunction b/file/datapacks/music_quiz_template/data/func/function/text_list/f1.mcfunction new file mode 100644 index 0000000..12fbad7 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/func/function/text_list/f1.mcfunction @@ -0,0 +1,16 @@ +$data modify storage func:temp space.text set string storage func:temp text $(index) $(index_next) +execute store result score result func.temp run function func:is_space with storage func:temp space + +$execute store result score result2 func.temp run function func:is_index {l1:"half",l2:"result",index:$(index)} + +execute if score result2 func.temp matches 0 if score result func.temp matches 0 run data modify storage func:temp text_list append value "■" +execute if score result2 func.temp matches 0 if score result func.temp matches 1 run data modify storage func:temp text_list append from storage func:temp space.text +execute if score result2 func.temp matches 1 run data modify storage func:temp text_list append from storage func:temp space.text + +function func:plus {name:"index",plus:1} +function func:plus {name:"index_next",plus:1} + +execute store result score result func.temp run function func:comp_num {n1:"index",n2:"length"} +execute if score result func.temp matches 1 run return 1 + +function func:text_list/f1 with storage func:temp diff --git a/file/datapacks/music_quiz_template/data/minecraft/tags/function/load.json b/file/datapacks/music_quiz_template/data/minecraft/tags/function/load.json new file mode 100644 index 0000000..5c47604 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/minecraft/tags/function/load.json @@ -0,0 +1,5 @@ +{ + "values": [ + "mq:load" + ] +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/minecraft/tags/function/tick.json b/file/datapacks/music_quiz_template/data/minecraft/tags/function/tick.json new file mode 100644 index 0000000..f996dbc --- /dev/null +++ b/file/datapacks/music_quiz_template/data/minecraft/tags/function/tick.json @@ -0,0 +1,5 @@ +{ + "values": [ + "mq:tick" + ] +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/mq/advancement/player/login.json b/file/datapacks/music_quiz_template/data/mq/advancement/player/login.json new file mode 100644 index 0000000..8f1b360 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/advancement/player/login.json @@ -0,0 +1,23 @@ +{ + "criteria": { + "placed_something": { + "trigger": "minecraft:impossible", + "conditions": {} + } + }, + "rewards": { + "function": "mq:players/login" + }, + "parent": "minecraft:story/root", + "display": { + "icon": { + "id": "minecraft:stone" + }, + "title": "로그인 감지", + "description": "서버 접속을 감지", + "hidden": false, + "announce_to_chat": true, + "show_toast": true, + "frame": "task" + } +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/mq/dialog/page1.json b/file/datapacks/music_quiz_template/data/mq/dialog/page1.json new file mode 100644 index 0000000..163cb80 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/dialog/page1.json @@ -0,0 +1,46 @@ +{ + "type": "minecraft:confirmation", + "title": { + "text": "음악퀴즈", + "bold": true + }, + "body": [ + { + "type": "minecraft:plain_message", + "contents": { + "text": "음악퀴즈 설명", + "bold": true + } + }, + { + "type": "minecraft:plain_message", + "contents": { + "text": "\n1. 정답입력시에 채팅으로 입력해주시면 됩니다.\n[ 띄어쓰기, 영어 대소문자, 특수문자 ]\n상관없이 입력하셔도 인식 됩니다.\n\n2. 모든 소리는 날씨 소리로 조절할수 있습니다.\n\n3. 게임시작후 버튼들은 과반수(절반이상)가 눌러야 작동합니다.\n\n4. 힌트는 특수문자 제외 정답의 절반이 가려져서 나옵니다.\n힌트는 여러번 받을수 있고,\n받을때마다 가려지는 부분이 달라집니다." + }, + "width": 300 + } + ], + "inputs": [], + "can_close_with_escape": true, + "pause": false, + "after_action": "close", + "yes": { + "label": { + "text": "취소", + "type": "text" + }, + "action": { + "type": "minecraft:run_command", + "command": "trigger cancel" + } + }, + "no": { + "label": { + "text": "다음 페이지 ->" + }, + "action": { + "type": "minecraft:show_dialog", + "dialog": "mq:page2" + } + } +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/mq/dialog/page2.json b/file/datapacks/music_quiz_template/data/mq/dialog/page2.json new file mode 100644 index 0000000..04f7544 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/dialog/page2.json @@ -0,0 +1,45 @@ +{ + "type": "minecraft:confirmation", + "title": { + "text": "음악퀴즈", + "bold": true + }, + "body": [ + { + "type": "minecraft:plain_message", + "contents": { + "text": "음악퀴즈 설명", + "bold": true + } + }, + { + "type": "minecraft:plain_message", + "contents": { + "text": "\n5. 다시듣기는 노래가 끝까지 다 재생되었거나,\n다시 처음부분부터 들어보고싶을때\n누르면 좋습니다.\n\n6. 뒤에있는 \"소리 테스트\" 버튼으로\n미리 소리크기를 들어보고 조절할수있습니다." + } + } + ], + "inputs": [], + "can_close_with_escape": true, + "pause": false, + "after_action": "close", + "yes": { + "label": { + "text": "<- 이전 페이지", + "type": "text" + }, + "action": { + "type": "minecraft:show_dialog", + "dialog": "mq:page1" + } + }, + "no": { + "label": { + "text": "다음 페이지 ->" + }, + "action": { + "type": "minecraft:show_dialog", + "dialog": "mq:page3" + } + } +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/mq/dialog/page3.json b/file/datapacks/music_quiz_template/data/mq/dialog/page3.json new file mode 100644 index 0000000..dd471d9 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/dialog/page3.json @@ -0,0 +1,46 @@ +{ + "type": "minecraft:confirmation", + "title": { + "text": "음악퀴즈", + "bold": true + }, + "body": [ + { + "type": "minecraft:plain_message", + "contents": { + "text": "음악퀴즈 설명", + "bold": true + } + }, + { + "type": "minecraft:plain_message", + "contents": { + "text": "\n재미있게 즐겨주세요." + } + } + ], + "inputs": [], + "can_close_with_escape": true, + "pause": false, + "after_action": "close", + "yes": { + "label": { + "text": "<- 이전 페이지", + "type": "text" + }, + "action": { + "type": "minecraft:show_dialog", + "dialog": "mq:page2" + } + }, + "no": { + "label": { + "text": "준비완료", + "bold": true + }, + "action": { + "type": "minecraft:run_command", + "command": "trigger ready" + } + } +} \ No newline at end of file diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/hint.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/hint.mcfunction new file mode 100644 index 0000000..34a2173 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/hint.mcfunction @@ -0,0 +1,14 @@ +scoreboard players reset @a hint + +execute if score init main matches 0 run return run function mq:tellraw {"text":"아직 퀴즈가 시작되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 1..4 run return run function mq:tellraw {"text":"아직 힌트를 받을 수 없습니다.","color":"red",msg:'""'} +execute if score init main matches 6.. run return run function mq:tellraw {"text":"아직 다음노래가 재생되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'} + +execute if score init main matches 5 run data modify storage mq:main hint.text set from storage mq:main answer.title +execute if score init main matches 5 run data modify storage mq:main hint.hint set value "" +execute if score init main matches 5 run function func:hint with storage mq:main hint +execute if score init main matches 5 run data modify storage mq:main hint.hint set from storage func:temp join.text +execute if score init main matches 5 run function mq:tellraw {"text":"","color":"black",msg:'""'} +execute if score init main matches 5 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"힌트: ","color":"aqua","bold":true},{"storage":"mq:main","nbt":"hint.hint","color": "yellow","bold": true}]} +execute if score init main matches 5 run function mq:tellraw {"text":"","color":"black",msg:'""'} diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/replay.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/replay.mcfunction new file mode 100644 index 0000000..59383f1 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/replay.mcfunction @@ -0,0 +1,9 @@ +scoreboard players reset @a replay + +execute if score init main matches 0 run return run function mq:tellraw {"text":"아직 퀴즈가 시작되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 1..4 run return run function mq:tellraw {"text":"아직 노래가 재생되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 6.. run return run function mq:tellraw {"text":"아직 다음노래가 재생되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'} + +execute if score init main matches 5 run function mq:quiz/stop_sound +execute if score init main matches 5 run function mq:quiz/play_sound diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/skip.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/skip.mcfunction new file mode 100644 index 0000000..4f91855 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/skip.mcfunction @@ -0,0 +1,9 @@ +scoreboard players reset @a skip + +execute if score init main matches 0 run return run function mq:tellraw {"text":"아직 퀴즈가 시작되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 1..4 run return run function mq:tellraw {"text":"아직 스킵 할수없습니다.","color":"red",msg:'""'} +execute if score init main matches 6.. run return run function mq:tellraw {"text":"아직 다음노래가 재생되지 않았습니다.","color":"red",msg:'""'} +execute if score init main matches 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'} + +execute if score init main matches 5 run scoreboard players set skip buttons -2 +execute if score init main matches 5 run function mq:quiz/correct diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/start.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/start.mcfunction new file mode 100644 index 0000000..d2a7220 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/start.mcfunction @@ -0,0 +1,10 @@ +execute if score init main matches 10 run return run function mq:tellraw {"text":"퀴즈가 완전히 종료된후 시작해주세요.","color":"red","msg":""} + +setblock ~ ~ ~ minecraft:air + +function mq:quiz/stop_sound + +$scoreboard players set max_index main $(max_index) +scoreboard players set init main 1 + +dialog show @a mq:page1 diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/stop.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/stop.mcfunction new file mode 100644 index 0000000..f2ba1d6 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/stop.mcfunction @@ -0,0 +1,59 @@ +scoreboard players set index main 0 +$scoreboard players set max_index main $(max_index) +scoreboard players set score main 0 +scoreboard players set init main 0 +scoreboard players set timer main 0 + +scoreboard players set start buttons -1 +scoreboard players set stop buttons -1 +scoreboard players set skip buttons -1 +scoreboard players set hint buttons -1 +scoreboard players set replay buttons -1 +scoreboard players set test buttons -1 + +scoreboard players reset @a answer + +# 트리거 시작 +scoreboard objectives remove ready +scoreboard objectives add ready trigger + +scoreboard objectives remove cancel +scoreboard objectives add cancel trigger + +scoreboard objectives remove stop +scoreboard objectives add stop trigger + +scoreboard objectives remove skip +scoreboard objectives add skip trigger + +scoreboard objectives remove hint +scoreboard objectives add hint trigger + +scoreboard objectives remove replay +scoreboard objectives add replay trigger +# 트리거 끝 + +scoreboard objectives setdisplay sidebar +scoreboard objectives remove score +scoreboard objectives add score dummy {"text":"점수","bold":true} +scoreboard objectives setdisplay sidebar score + +dialog clear @a + +bossbar set mq:process name [{"text":"진행도: ","color": "yellow","bold": true},{"score":{"name":"index","objective": "main"},"color": "yellow","bold": true},{"text":"/","color": "yellow","bold": true},{"score":{"name":"max_index","objective": "main"},"color": "yellow","bold": true}] +$bossbar set mq:process max $(max_index) +bossbar set mq:process value 0 +bossbar set mq:process color pink +bossbar set mq:process visible false +bossbar set mq:process style notched_10 +bossbar set mq:process players @a + +# 대기 상태 marker 1개만 소환 (answer.title="음악퀴즈" 가 sentinel) +data modify storage mq:main answer set value {title:"음악퀴즈", alias:[]} +data modify storage mq:tmp marker_call set from storage mq:main marker +data modify storage mq:tmp marker_call.name set value "음악퀴즈" +data modify storage mq:tmp marker_call.alias set value [] +function mq:quiz/macro/summon with storage mq:tmp marker_call + +function mq:quiz/stop_sound +function mq:images/clear diff --git a/file/datapacks/music_quiz_template/data/mq/function/commands/test.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/commands/test.mcfunction new file mode 100644 index 0000000..2e3d15d --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/commands/test.mcfunction @@ -0,0 +1,5 @@ +stopsound @a block minecraft:block.stone_button.click_on +function mq:tellraw {"text":"띵!!!","color":"white","msg":'""'} +execute as @a at @s run playsound minecraft:block.note_block.bell weather @s ~ ~ ~ 1 0.9 +execute as @a at @s run playsound minecraft:block.note_block.bell weather @s ~ ~ ~ 1 0.9 +execute as @a at @s run playsound minecraft:block.note_block.bell weather @s ~ ~ ~ 1 0.9 diff --git a/file/datapacks/music_quiz_template/data/mq/function/images/clear.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/images/clear.mcfunction new file mode 100644 index 0000000..01210ae --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/images/clear.mcfunction @@ -0,0 +1 @@ +kill @e[type=minecraft:painting,tag=mq_cover] diff --git a/file/datapacks/music_quiz_template/data/mq/function/images/macro/show.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/images/macro/show.mcfunction new file mode 100644 index 0000000..ec54eb8 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/images/macro/show.mcfunction @@ -0,0 +1 @@ +$summon minecraft:painting $(x) $(y) $(z) {variant:"$(namespace):$(cover)",facing:$(facing)b,Tags:["mq","mq_cover"]} diff --git a/file/datapacks/music_quiz_template/data/mq/function/images/show.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/images/show.mcfunction new file mode 100644 index 0000000..bed4b7d --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/images/show.mcfunction @@ -0,0 +1,3 @@ +data modify storage mq:tmp painting set from storage mq:main image +data modify storage mq:tmp painting.cover set from storage mq:main answer.cover +function mq:images/macro/show with storage mq:tmp painting diff --git a/file/datapacks/music_quiz_template/data/mq/function/init/buttons.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/init/buttons.mcfunction new file mode 100644 index 0000000..5b7f752 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/init/buttons.mcfunction @@ -0,0 +1,7 @@ +data modify storage mq:main button_defs set value [] +data modify storage mq:main button_defs append value {n:"start", x:140, y:62, z:-225, f:"south", c:"function mq:commands/start with storage mq:main"} +data modify storage mq:main button_defs append value {n:"stop", x:142, y:62, z:-225, f:"south", c:"function mq:commands/stop with storage mq:main"} +data modify storage mq:main button_defs append value {n:"skip", x:144, y:62, z:-225, f:"south", c:"function mq:commands/skip"} +data modify storage mq:main button_defs append value {n:"hint", x:146, y:62, z:-225, f:"south", c:"function mq:commands/hint"} +data modify storage mq:main button_defs append value {n:"replay", x:148, y:62, z:-225, f:"south", c:"function mq:commands/replay"} +data modify storage mq:main button_defs append value {n:"test", x:144, y:62, z:-213, f:"north", c:"function mq:commands/test"} diff --git a/file/datapacks/music_quiz_template/data/mq/function/init/config.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/init/config.mcfunction new file mode 100644 index 0000000..2c41573 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/init/config.mcfunction @@ -0,0 +1,23 @@ +# 음악퀴즈 주제 — tellraw 접두사([ 이름 ])와 사이드바 표시에 사용 +data modify storage mq:main title set value "음악퀴즈" + +# 플레이어 접속 시 텔레포트 위치 (x y z, r=yaw, f=pitch) +data modify storage mq:main spawn set value {x: 144, y: 61, z: -219, r: 180, f: 0} + +# 음원 재생 — minecraft_launcher 리소스팩의 musicquiz:track_NN 사운드 이벤트 +# namespace — 리소스팩 네임스페이스 (기본 "musicquiz") +# source — /playsound 채널. stopsound 와 동일해야 함 (기본 "weather") +# volume — 기본 음량. 곡별 override 는 init/songs.mcfunction 의 volume 필드 사용 +# pitch — 1.0 = 원본 속도 +data modify storage mq:main audio set value {namespace: "musicquiz", source: "weather", volume: 1.0, pitch: 1.0} + +# 정답 페인팅 — minecraft_launcher 리소스팩의 musicquiz:cover_NN painting_variant +# namespace — painting_variant 네임스페이스 (기본 "musicquiz") +# x,y,z — 페인팅 entity 좌표 (벽면 앞쪽 블록 위치) +# facing — 페인팅이 바라보는 방향: south=0 / west=1 / north=2 / east=3 +data modify storage mq:main image set value {namespace: "musicquiz", x: 144, y: 84, z: -261, facing: 0b} + +# 정답 입력용 marker entity 소환 좌표 +data modify storage mq:main marker set value {x: 144, y: 59, z: -219} + +# 곡 개수 max_index 는 init/songs.mcfunction 의 길이로 자동 계산됨 diff --git a/file/datapacks/music_quiz_template/data/mq/function/init/triggers.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/init/triggers.mcfunction new file mode 100644 index 0000000..788b27e --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/init/triggers.mcfunction @@ -0,0 +1,5 @@ +data modify storage mq:main trigger_defs set value [] +data modify storage mq:main trigger_defs append value {n:"stop", n2:"중지", c:"function mq:commands/stop with storage mq:main"} +data modify storage mq:main trigger_defs append value {n:"skip", n2:"스킵", c:"function mq:commands/skip"} +data modify storage mq:main trigger_defs append value {n:"hint", n2:"힌트", c:"function mq:commands/hint"} +data modify storage mq:main trigger_defs append value {n:"replay", n2:"다시재생", c:"function mq:commands/replay"} diff --git a/file/datapacks/music_quiz_template/data/mq/function/load.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/load.mcfunction new file mode 100644 index 0000000..9845e29 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/load.mcfunction @@ -0,0 +1,29 @@ +data modify storage mq:main answer set value {title:"", author:"", alias:[]} +data merge storage func:temp {} +data merge storage mq:tmp {} + +function mq:init/config +function mq:init/songs +function mq:init/buttons +function mq:init/triggers + +function mq:tellraw {"text":"서버 리로드 성공!","color":"white","msg":'""'} + +scoreboard objectives remove func.temp +scoreboard objectives remove main +scoreboard objectives remove buttons +scoreboard objectives remove answer +scoreboard objectives remove leave_game + +scoreboard objectives add func.temp dummy +scoreboard objectives add main dummy +scoreboard objectives add buttons dummy +scoreboard objectives add answer dummy +scoreboard objectives add leave_game custom:leave_game + +scoreboard players set two func.temp 2 + +bossbar add mq:process [{"text":"진행도: ","color": "yellow","bold": true},{"text":"0","color": "yellow","bold": true},{"text":"/","color": "yellow","bold": true},{"text":"0","color": "yellow","bold": true}] + +function mq:commands/stop with storage mq:main +function mq:players/login with storage mq:main spawn diff --git a/file/datapacks/music_quiz_template/data/mq/function/players/login.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/players/login.mcfunction new file mode 100644 index 0000000..cde23bc --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/players/login.mcfunction @@ -0,0 +1,10 @@ +tag @s add player +scoreboard players reset @s leave_game + +title @s times 10t 80t 10t +title @s subtitle "" +title @s title "" + +$setworldspawn $(x) $(y) $(z) $(r) $(f) +$tp @s $(x) $(y) $(z) $(r) $(f) +gamemode adventure @s diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/correct.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/correct.mcfunction new file mode 100644 index 0000000..a667333 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/correct.mcfunction @@ -0,0 +1,32 @@ +scoreboard players set init main 6 + +scoreboard players set @s answer 2 + +function mq:tellraw {"text":"","color":"black","msg":""} +function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답: ","color": "aqua"},{"storage":"mq:main","nbt":"answer.title","color": "yellow","bold": true}]} +function mq:tellraw {"text":"","color":"black",msg:[{"text":"가수: ","color":"aqua"},{"storage":"mq:main","nbt":"answer.author","color": "yellow","bold": true}]} +execute if score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"text":"스킵","color": "yellow","bold": true}]} +execute unless score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"selector":"@s","color": "yellow","bold": true}]} +function mq:tellraw {"text":"","color":"black",msg:[{"text": "( 15초뒤 다음문제로 넘어갑니다. )","color": "gray"}]} +function mq:tellraw {"text":"","color":"black","msg":""} + +title @a subtitle [{"text":"정답: ","color": "aqua"},{"storage":"mq:main","nbt":"answer.title","color": "yellow","bold": true}] +title @a title {"text":""} + +scoreboard players set @a ready 0 +scoreboard players set @a stop 0 +scoreboard players set @a skip 0 +scoreboard players set @a hint 0 +scoreboard players set @a replay 0 + +execute if score skip buttons matches -2 run scoreboard players add 스킵 score 1 +execute unless score skip buttons matches -2 run scoreboard players add @s score 1 + +scoreboard players set stop buttons -3 +scoreboard players set skip buttons -3 +scoreboard players set hint buttons -3 +scoreboard players set replay buttons -3 + +scoreboard players set timer main 1 + +function mq:images/show diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/end.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/end.mcfunction new file mode 100644 index 0000000..271354e --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/end.mcfunction @@ -0,0 +1,3 @@ +scoreboard players set init main 10 + +scoreboard players set timer main 1 diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/play_sound.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/play_sound.mcfunction new file mode 100644 index 0000000..b0def6a --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/play_sound.mcfunction @@ -0,0 +1 @@ +$execute as @a at @s run playsound $(namespace):$(track) $(source) @s ~ ~ ~ $(volume) $(pitch) diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/setanswer.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/setanswer.mcfunction new file mode 100644 index 0000000..b36889d --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/setanswer.mcfunction @@ -0,0 +1,3 @@ +$data modify storage mq:main answer set from storage mq:main songs[$(idx)] +$data modify storage mq:main answer.track set value "track_$(pad)$(num)" +$data modify storage mq:main answer.cover set value "cover_$(pad)$(num)" diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/stop_sound.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/stop_sound.mcfunction new file mode 100644 index 0000000..7f1364b --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/stop_sound.mcfunction @@ -0,0 +1 @@ +$stopsound @a $(source) diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon.mcfunction new file mode 100644 index 0000000..ecade67 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon.mcfunction @@ -0,0 +1,9 @@ +$execute unless data storage mq:main {answer:{title:"음악퀴즈"}} run summon minecraft:marker $(x) $(y) $(z) {Tags:["mq","default"],CustomName:"정답입력시작"} +$summon minecraft:marker $(x) $(y) $(z) {Tags:["mq","default"],CustomName:"$(name)"} + +execute store result score length func.temp run data get storage mq:tmp marker_call.alias +execute if score length func.temp matches 1.. run data modify storage mq:tmp marker_call.name set from storage mq:tmp marker_call.alias[0] +execute if score length func.temp matches 1.. run data remove storage mq:tmp marker_call.alias[0] +execute if score length func.temp matches 1.. run function mq:quiz/macro/summon2 with storage mq:tmp marker_call + +$execute unless data storage mq:main {answer:{title:"음악퀴즈"}} run summon minecraft:marker $(x) $(y) $(z) {Tags:["mq","default"],CustomName:"정답입력종료"} diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon2.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon2.mcfunction new file mode 100644 index 0000000..062d8e1 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/macro/summon2.mcfunction @@ -0,0 +1,6 @@ +$summon minecraft:marker $(x) $(y) $(z) {Tags:["mq","default"],CustomName:"$(name)"} + +execute store result score length func.temp run data get storage mq:tmp marker_call.alias +execute if score length func.temp matches 1.. run data modify storage mq:tmp marker_call.name set from storage mq:tmp marker_call.alias[0] +execute if score length func.temp matches 1.. run data remove storage mq:tmp marker_call.alias[0] +execute if score length func.temp matches 1.. run function mq:quiz/macro/summon2 with storage mq:tmp marker_call diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/play_sound.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/play_sound.mcfunction new file mode 100644 index 0000000..be1f1a7 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/play_sound.mcfunction @@ -0,0 +1,5 @@ +data modify storage mq:tmp playsound set from storage mq:main audio +data modify storage mq:tmp playsound.track set from storage mq:main answer.track +# 곡 단위 volume override — songs[i].volume 가 없으면 audio.volume 그대로 유지 (no-op) +data modify storage mq:tmp playsound.volume set from storage mq:main answer.volume +function mq:quiz/macro/play_sound with storage mq:tmp playsound diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/select.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/select.mcfunction new file mode 100644 index 0000000..317ba0a --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/select.mcfunction @@ -0,0 +1,20 @@ +scoreboard players set timer main 0 + +execute if score index main >= max_index main run return run function mq:quiz/end with storage mq:main + +scoreboard players add index main 1 + +bossbar set mq:process name [{"text":"진행도: ","color": "yellow","bold": true},{"score":{"name":"index","objective": "main"},"color": "yellow","bold": true},{"text":"/","color": "yellow","bold": true},{"score":{"name":"max_index","objective": "main"},"color": "yellow","bold": true}] +bossbar set mq:process players @a +execute store result bossbar mq:process value run scoreboard players get index main + +# tmp.{idx (0-based, songs[] 인덱스), num (1-based, track_NN), pad ("0"|"")} 구성 +execute store result storage mq:tmp num int 1 run scoreboard players get index main +scoreboard players operation song_idx func.temp = index main +scoreboard players remove song_idx func.temp 1 +execute store result storage mq:tmp idx int 1 run scoreboard players get song_idx func.temp + +execute if score index main matches 1..9 run data modify storage mq:tmp pad set value "0" +execute unless score index main matches 1..9 run data modify storage mq:tmp pad set value "" + +function mq:quiz/setanswer diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/setanswer.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/setanswer.mcfunction new file mode 100644 index 0000000..4cd712d --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/setanswer.mcfunction @@ -0,0 +1,17 @@ +# songs[$(idx)] → answer 로 복사하고, 트랙/커버 id 부여 +function mq:quiz/macro/setanswer with storage mq:tmp + +# 정답 marker entity 소환 (좌표 + name/alias 합쳐서 macro 호출) +data modify storage mq:tmp marker_call set from storage mq:main marker +data modify storage mq:tmp marker_call.name set from storage mq:main answer.title +data modify storage mq:tmp marker_call.alias set from storage mq:main answer.alias +function mq:quiz/macro/summon with storage mq:tmp marker_call + +scoreboard players set stop buttons -1 +scoreboard players set skip buttons -1 +scoreboard players set hint buttons -1 +scoreboard players set replay buttons -1 + +scoreboard players set init main 5 + +function mq:quiz/play_sound diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/start.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/start.mcfunction new file mode 100644 index 0000000..f651eb1 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/start.mcfunction @@ -0,0 +1,6 @@ +scoreboard players set init main 2 + +scoreboard players set index main 0 +bossbar set mq:process visible true + +scoreboard players set timer main 1 diff --git a/file/datapacks/music_quiz_template/data/mq/function/quiz/stop_sound.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/quiz/stop_sound.mcfunction new file mode 100644 index 0000000..43b9631 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/quiz/stop_sound.mcfunction @@ -0,0 +1 @@ +function mq:quiz/macro/stop_sound with storage mq:main audio diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/btn.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/btn.mcfunction new file mode 100644 index 0000000..695e14b --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/btn.mcfunction @@ -0,0 +1,28 @@ +$execute if score $(n) buttons matches ..-2 run setblock $(x) $(y) $(z) minecraft:air +$execute if score $(n) buttons matches ..-2 run data modify entity @e[type=minecraft:interaction,tag=mq,tag=$(n),limit=1] response set value 0b +$execute if score $(n) buttons matches ..-2 run return 0 +$execute unless score $(n) buttons matches -1.. run scoreboard players set $(n) buttons -1 +$execute if score $(n) buttons matches -1 run setblock $(x) $(y) $(z) minecraft:stone_button[face=wall,facing=$(f),powered=false] +$execute if score $(n) buttons matches -1 positioned $(x) $(y) $(z) run setblock ~ ~-3 ~ minecraft:redstone_block +$execute if score $(n) buttons matches -1 positioned $(x) $(y) $(z) run setblock ~ ~-3 ~ minecraft:red_wool +$execute if score $(n) buttons matches -1 run scoreboard players set $(n) buttons 0 + +$execute if block $(x) $(y) $(z) minecraft:stone_button[face=wall,facing=$(f),powered=true] \ + if score $(n) buttons matches 0 \ + run scoreboard players set $(n) buttons 1 + +$execute if score $(n) buttons matches 1 unless entity @e[type=minecraft:interaction,tag=mq,tag=$(n),limit=1] positioned $(x) $(y) $(z) run $(c) + +$execute if score $(n) buttons matches 1 \ + run scoreboard players set $(n) buttons 2 + +$execute if block $(x) $(y) $(z) minecraft:stone_button[face=wall,facing=$(f),powered=false] \ + if score $(n) buttons matches 1.. \ + run scoreboard players set $(n) buttons 0 + +$execute as @e[type=minecraft:interaction,tag=mq,tag=$(n),limit=1] on target as @s positioned $(x) $(y) $(z) run playsound minecraft:block.stone_button.click_on block @s ~ ~ ~ 1 1 +$execute as @e[type=minecraft:interaction,tag=mq,tag=$(n),limit=1] on target as @s positioned $(x) $(y) $(z) if score init main matches 0 run $(c) +$execute as @e[type=minecraft:interaction,tag=mq,tag=$(n),limit=1] on target as @s positioned $(x) $(y) $(z) unless score init main matches 0 run trigger $(n) + +$execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] at @s run data remove entity @s attack +$execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] at @s run data remove entity @s interaction diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/handler.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/handler.mcfunction new file mode 100644 index 0000000..a3db64a --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/buttons/handler.mcfunction @@ -0,0 +1,6 @@ +function mq:repeat/buttons/btn with storage mq:main button_defs[0] +function mq:repeat/buttons/btn with storage mq:main button_defs[1] +function mq:repeat/buttons/btn with storage mq:main button_defs[2] +function mq:repeat/buttons/btn with storage mq:main button_defs[3] +function mq:repeat/buttons/btn with storage mq:main button_defs[4] +function mq:repeat/buttons/btn with storage mq:main button_defs[5] diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/check_answer.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/check_answer.mcfunction new file mode 100644 index 0000000..5599e04 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/check_answer.mcfunction @@ -0,0 +1,2 @@ +execute as @a[scores={answer=1}] run function mq:quiz/correct with storage mq:main answer +execute as @a[scores={answer=2}] run scoreboard players reset @a answer diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/players.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/players.mcfunction new file mode 100644 index 0000000..9de5e01 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/players.mcfunction @@ -0,0 +1,2 @@ +execute as @a[tag=!player] run function mq:players/login with storage mq:main spawn +execute as @a if score @s leave_game matches 1.. run function mq:players/login with storage mq:main spawn diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/timer.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/timer.mcfunction new file mode 100644 index 0000000..adbb1d1 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/timer.mcfunction @@ -0,0 +1,49 @@ +execute if score timer main matches 1.. run scoreboard players add timer main 1 + +execute unless score init main matches 2 \ + unless score init main matches 6 \ + unless score init main matches 10 \ + run scoreboard players set timer main 0 + +# start title timer +execute if score init main matches 2 if score timer main matches 20 run title @a title {"text":"3"} +execute if score init main matches 2 if score timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 + +execute if score init main matches 2 if score timer main matches 40 run title @a title {"text":"2"} +execute if score init main matches 2 if score timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 + +execute if score init main matches 2 if score timer main matches 60 run title @a title {"text":"1"} +execute if score init main matches 2 if score timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 +execute if score init main matches 2 if score timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1 + +execute if score init main matches 2 if score timer main matches 100 run title @a title {"text":""} +execute if score init main matches 2 if score timer main matches 100.. run function mq:quiz/select with storage mq:main + +# next song timer +execute if score init main matches 6 if score timer main matches 300 run title @a title {"text":""} +execute if score init main matches 6 if score timer main matches 290 run function mq:images/clear +execute if score init main matches 6 if score timer main matches 300.. run function mq:quiz/select with storage mq:main + +# endding timer +execute if score init main matches 10 if score timer main matches 60 run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"white","msg":""} +execute if score init main matches 10 if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 + +execute if score init main matches 10 if score timer main matches 180 run function mq:tellraw {"text":"퀴즈를 다시 시작하시려면 종료를 눌러주세요.","color":"white","msg":""} +execute if score init main matches 10 if score timer main matches 120 as @a at @s run scoreboard players set stop buttons -1 +execute if score init main matches 10 if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 + +execute if score init main matches 10 if score timer main matches 120 run function mq:tellraw {"text":"플레이 해주셔서 감사합니다.","color":"white","msg":""} +execute if score init main matches 10 if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 +execute if score init main matches 10 if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1 + +execute if score init main matches 10 if score timer main matches 200.. run scoreboard players set init main 11 diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/handler.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/handler.mcfunction new file mode 100644 index 0000000..1889239 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/handler.mcfunction @@ -0,0 +1,24 @@ +execute if score init main matches 0..1 run scoreboard players enable @a ready +execute if score init main matches 0..1 as @a if score @s ready matches 1 run function mq:tellraw {"text":"","color":"black",msg:[{"selector":"@s","color": "yellow","bold": true},{"text":" : ","color":"gray"},{"text":"준비완료","color":"white"}]} +execute if score init main matches 0..1 as @a if score @s ready matches 1 run scoreboard players set @s ready 2 +execute if score init main matches 0..1 as @a if score @s ready matches 3 run function mq:tellraw {"text":"","color":"black",msg:[{"selector":"@s","color": "yellow","bold": true},{"text":" : ","color":"gray"},{"text":"이미 준비완료 상태입니다.","color": "red"}]} +execute if score init main matches 0..1 as @a if score @s ready matches 3 run scoreboard players set @s ready 2 + + +execute if score init main matches 0..1 run scoreboard players enable @a cancel +execute if score init main matches 0..1 as @a if score @s cancel matches 1 run function mq:tellraw {"text":"","color":"black",msg:[{"selector":"@s","color": "yellow","bold": true},{"text":" : ","color":"gray"},{"text":"취소를 선택하셨습니다.","color": "red"}]} +execute if score init main matches 0..1 as @a if score @s cancel matches 1 run function mq:commands/stop with storage mq:main + + +execute if score init main matches 0..1 store result score max_player ready if entity @a +execute if score init main matches 0..1 store result score ready_player ready if entity @a[scores={ready=2..}] +execute if score init main matches 0..1 \ + unless score max_player ready matches 0 \ + if score max_player ready = ready_player ready \ + run function mq:quiz/start with storage mq:main + + +function mq:repeat/triggers/trigger with storage mq:main trigger_defs[0] +function mq:repeat/triggers/trigger with storage mq:main trigger_defs[1] +function mq:repeat/triggers/trigger with storage mq:main trigger_defs[2] +function mq:repeat/triggers/trigger with storage mq:main trigger_defs[3] diff --git a/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/trigger.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/trigger.mcfunction new file mode 100644 index 0000000..08a10dd --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/repeat/triggers/trigger.mcfunction @@ -0,0 +1,27 @@ +$scoreboard players enable @a $(n) + +$execute unless score init main matches 5 as @a if score @s $(n) matches 1.. run scoreboard players reset @s $(n) +execute unless score init main matches 5 run return 0 + +$execute store result score real_max_player $(n) if entity @a + +$execute store result score rest_player $(n) if entity @a +$execute unless score rest_player $(n) matches 0 run scoreboard players operation rest_player $(n) %= two func.temp +$execute store result score max_player $(n) if entity @a +$execute unless score real_max_player $(n) matches 0 run scoreboard players operation max_player $(n) /= two func.temp +$execute unless score real_max_player $(n) matches 0 run scoreboard players operation max_player $(n) += rest_player $(n) +$execute store result score $(n)_player $(n) if entity @a[scores={$(n)=2..}] +$execute store result score $(n)_player_add $(n) if entity @a[scores={$(n)=2..}] +$execute run scoreboard players add $(n)_player_add $(n) 1 + +$execute as @a if score @s $(n) matches 1 run function mq:tellraw {"text":"","color":"black",msg:[{"selector":"@s","color": "yellow","bold": true}," : ",{"text":"$(n2) 투표 완료","color": "white"}, \ + {"text":" (","color":"gray"},{"score":{"name":"$(n)_player_add","objective": "$(n)"},"color":"gray"},{"text":"/","color":"gray"},{"score":{"name":"max_player","objective": "$(n)"},"color":"gray"},{"text":")","color":"gray"}]} +$execute as @a if score @s $(n) matches 1 run scoreboard players set @s $(n) 2 +$execute as @a if score @s $(n) matches 3 run function mq:tellraw {"text":"","color":"black",msg:[{"selector":"@s","color": "yellow","bold": true}," : ",{"text":"이미 $(n2)투표를 하셨습니다.","color": "red"}]} +$execute as @a if score @s $(n) matches 3 run scoreboard players set @s $(n) 2 + +$execute store result score $(n)_player $(n) if entity @a[scores={$(n)=2..}] + +$execute unless score real_max_player $(n) matches 0 \ + if score max_player $(n) = $(n)_player $(n) \ + run $(c) diff --git a/file/datapacks/music_quiz_template/data/mq/function/tellraw.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/tellraw.mcfunction new file mode 100644 index 0000000..e8a04f7 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/tellraw.mcfunction @@ -0,0 +1 @@ +$tellraw @a ["",{"text":"[ ","bold":true,"color":"gray"},{"storage":"mq:main","nbt":"title","bold":true,"color":"dark_green"},{"text":" ]","bold":true,"color":"gray"},{"text":" "},{"text":"$(text)","color":"$(color)"},$(msg)] diff --git a/file/datapacks/music_quiz_template/data/mq/function/tick.mcfunction b/file/datapacks/music_quiz_template/data/mq/function/tick.mcfunction new file mode 100644 index 0000000..75b4c15 --- /dev/null +++ b/file/datapacks/music_quiz_template/data/mq/function/tick.mcfunction @@ -0,0 +1,6 @@ +function mq:repeat/players +function mq:repeat/buttons/handler +function mq:repeat/triggers/handler + +execute if score init main matches 2.. run function mq:repeat/timer +execute if score init main matches 5..6 run function mq:repeat/check_answer diff --git a/file/datapacks/music_quiz_template/pack.mcmeta b/file/datapacks/music_quiz_template/pack.mcmeta new file mode 100644 index 0000000..cde6075 --- /dev/null +++ b/file/datapacks/music_quiz_template/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 75, + "description": "음악퀴즈용 데이터팩입니다." + } +} \ No newline at end of file diff --git a/locales/server/ko-kr.json b/locales/server/ko-kr.json index 0ad041e..31034cb 100644 --- a/locales/server/ko-kr.json +++ b/locales/server/ko-kr.json @@ -136,11 +136,9 @@ "pickedNone": "선택된 음악퀴즈 없음", "pickedLabel": "선택: {{name}}", "totalCount": "총 {{count}}개의 음악을 찾았습니다.", - "export": "데이터팩 출력", - "copy": "복사", - "copied": "복사됨", - "exporting": "출력 중…", - "exported": "출력 완료", + "export": "데이터팩 zip 다운로드", + "exporting": "다운로드 준비 중…", + "exported": "다운로드를 시작했습니다.", "failed": "실패: {{message}}", "modalPickTitle": "음악퀴즈 선택" }, @@ -161,14 +159,5 @@ "ytdlpVideoFailed": "yt-dlp 영상 조회 실패 (code={{code}}): {{detail}}", "ytdlpPlaylistFailed": "yt-dlp 플레이리스트 조회 실패 (code={{code}}): {{detail}}", "tooManyRedirects": "redirect 가 너무 많습니다." - }, - "datapackOutput": { - "header": "# === musicquiz: {{name}} ===", - "summary": "# 총 {{musicCount}}곡 / 사진 {{imageCount}}장", - "initLine": "say [musicquiz] 데이터팩 초기화", - "placeholder": "# 곡별 placeholder. 실제 포맷 확정되면 교체 예정.", - "trackLine": "# {{index}}. {{title}} - {{artist}} ({{duration}}s)", - "titleFallback": "(제목 없음)", - "artistFallback": "(가수 미상)" } } diff --git a/src/server/datapack.ts b/src/server/datapack.ts new file mode 100644 index 0000000..d7a6e86 --- /dev/null +++ b/src/server/datapack.ts @@ -0,0 +1,79 @@ +import path from 'node:path' +import { Readable } from 'node:stream' +import archiver from 'archiver' +import type { Response } from 'express' +import { fileDatapacksDirPath } from '../shared/paths.js' +import type { MusicListEntry, PackList } from '../shared/types.js' + +/** music_quiz/ 정적 템플릿 디렉터리. (songs.mcfunction 만 동적으로 생성) */ +const TEMPLATE_DIR = path.join(fileDatapacksDirPath, 'music_quiz_template') +const SONGS_PATH_IN_ZIP = 'music_quiz/data/mq/function/init/songs.mcfunction' + +/** SNBT 문자열 리터럴 안에 들어갈 문자열을 escape. */ +function escapeSnbtString(input: string): string { + return input.replace(/\\/g, '\\\\').replace(/"/g, '\\"') +} + +/** alias 배열을 SNBT 리스트 리터럴로 변환. 빈 배열도 `[]` 로 출력. */ +function aliasListSnbt(aliases: string[]): string { + if (!Array.isArray(aliases) || aliases.length === 0) return '[]' + const parts = aliases.map((a) => `"${escapeSnbtString(a)}"`) + return `[${parts.join(',')}]` +} + +/** 한 곡(MusicListEntry) → `{title:"...", author:"...", alias:[...]}` SNBT. */ +function entrySnbt(entry: MusicListEntry): string { + const title = escapeSnbtString(entry.title ?? '') + // launcher 의 artist → 데이터팩 SNBT 의 author. 빈 값은 빈 문자열로 그대로 둔다. + const author = escapeSnbtString(entry.artist ?? '') + const alias = aliasListSnbt(entry.aliases ?? []) + return `{title:"${title}", author:"${author}", alias:${alias}}` +} + +/** list.music 으로부터 `data/mq/function/init/songs.mcfunction` 본문을 생성. */ +export function buildSongsMcfunction(list: PackList): string { + const lines: string[] = [] + lines.push('# 곡 한 개 = 한 줄.') + lines.push('# 필수 — title, author, alias') + lines.push('# 선택 — volume (이 곡만의 /playsound 음량. 미지정시 init/config.mcfunction') + lines.push('# 의 audio.volume 사용)') + lines.push('# 곡 순서가 리소스팩의 track_NN / cover_NN 인덱스와 1:1 매칭된다.') + lines.push('# 예) {title:"Quiet Song", author:"...", alias:[...], volume:2.0}') + lines.push('data modify storage mq:main songs set value []') + for (const entry of list.music) { + lines.push(`data modify storage mq:main songs append value ${entrySnbt(entry)}`) + } + lines.push('') + lines.push('# 곡 개수는 songs 배열 길이에서 자동 계산됨') + lines.push('execute store result storage mq:main max_index int 1 run data get storage mq:main songs') + return lines.join('\n') + '\n' +} + +/** music_quiz 데이터팩 zip 을 Response 로 스트리밍. */ +export function streamMusicQuizZip(res: Response, packKey: string, list: PackList): void { + const fileName = `music_quiz_${packKey}.zip` + res.setHeader('Content-Type', 'application/zip') + res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`) + + const archive = archiver('zip', { zlib: { level: 9 } }) + archive.on('warning', (err) => { + if (err.code !== 'ENOENT') res.destroy(err) + }) + archive.on('error', (err) => { + res.destroy(err) + }) + archive.pipe(res) + + // 정적 템플릿 전체를 music_quiz/ 아래로 묶되 songs.mcfunction 만 제외. + archive.glob('**/*', { + cwd: TEMPLATE_DIR, + dot: false, + ignore: ['data/mq/function/init/songs.mcfunction'] + }, { prefix: 'music_quiz/' }) + + // 동적으로 만든 songs.mcfunction 을 추가. + const songsText = buildSongsMcfunction(list) + archive.append(Readable.from([songsText]), { name: SONGS_PATH_IN_ZIP }) + + void archive.finalize() +} diff --git a/src/server/routes/op.ts b/src/server/routes/op.ts index f0c2555..44e7737 100644 --- a/src/server/routes/op.ts +++ b/src/server/routes/op.ts @@ -17,6 +17,7 @@ import { fetchPlaylistEntries, fetchVideoMeta, YtDlpUnavailableError } from '../ import { requireAuth } from '../middleware/auth.js' import type { PackDefinition, PackList } from '../../shared/types.js' import { t } from '../i18n.js' +import { streamMusicQuizZip } from '../datapack.js' export const opRouter = Router() @@ -223,17 +224,19 @@ opRouter.post('/op/list/:packName/playlist', requireAuth, async (req, res) => { opRouter.get('/op/datapack', requireAuth, async (req, res, next) => { try { const keys = await listPackKeys() - const items = await Promise.all(keys.map(async (key) => ({ - key, - definition: await loadPackDefinition(key) - }))) + const items = await Promise.all(keys.map(async (key) => { + const definition = await loadPackDefinition(key) + const list = await loadPackList(key) + return { key, definition, musicCount: list.music.length } + })) res.render('op/datapack', { userId: req.session.userId, items }) } catch (error) { next(error) } }) -// 데이터팩 출력: 임시 포맷의 mcfunction 텍스트를 반환. +// 데이터팩 출력: mc_datapack 의 music_quiz/ 템플릿을 zip 으로 묶고, +// data/mq/function/init/songs.mcfunction 만 list.music 으로 새로 만들어 덮어쓴다. opRouter.get('/op/datapack/:packName/generate', requireAuth, async (req, res, next) => { try { const packKey = sanitizePackKey(pickFirstValue(req.params.packName)) @@ -243,25 +246,7 @@ opRouter.get('/op/datapack/:packName/generate', requireAuth, async (req, res, ne return } const list = await loadPackList(packKey) - const lines: string[] = [] - lines.push(t('datapackOutput.header', { name: definition.name })) - lines.push(t('datapackOutput.summary', { - musicCount: list.music.length, - imageCount: list.images.length - })) - lines.push(t('datapackOutput.initLine')) - lines.push(t('datapackOutput.placeholder')) - list.music.forEach((entry, index) => { - const title = entry.title || t('datapackOutput.titleFallback') - const artist = entry.artist || t('datapackOutput.artistFallback') - lines.push(t('datapackOutput.trackLine', { - index: index + 1, - title, - artist, - duration: entry.durationSec - })) - }) - res.type('text/plain; charset=utf-8').send(lines.join('\n') + '\n') + streamMusicQuizZip(res, packKey, list) } catch (error) { next(error) } diff --git a/src/shared/paths.ts b/src/shared/paths.ts index 30b0600..2dd9cb0 100644 --- a/src/shared/paths.ts +++ b/src/shared/paths.ts @@ -8,6 +8,7 @@ export const manifestDirPath = path.join(projectRoot, 'manifest') export const accountFilePath = path.join(projectRoot, 'account.json') export const fileDirPath = path.join(projectRoot, 'file') export const fileListDirPath = path.join(fileDirPath, 'list') +export const fileDatapacksDirPath = path.join(fileDirPath, 'datapacks') export const viewsDirPath = path.join(projectRoot, 'views') export const publicDirPath = path.join(projectRoot, 'public') diff --git a/views/op/datapack.ejs b/views/op/datapack.ejs index 351fbc1..c06f714 100644 --- a/views/op/datapack.ejs +++ b/views/op/datapack.ejs @@ -26,11 +26,8 @@ - - @@ -42,7 +39,10 @@
<% items.forEach(function (item) { %> -
+

<%= item.definition ? item.definition.name : item.key %>

<%= item.key %>.json

<% if (item.definition) { %> @@ -60,8 +60,6 @@