<%= item.definition ? item.definition.name : item.key %>
<%= item.key %>.json
<% if (item.definition) { %> @@ -60,8 +60,6 @@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 @@
<%= item.key %>.json
<% if (item.definition) { %> @@ -60,8 +60,6 @@