11 Commits

Author SHA1 Message Date
Claude (owner)
2f5038e4f0 music_quiz: btn_prep/btn 의 정렬용 다중 공백 제거 — Brigadier 파싱 에러 회피
btn_prep.mcfunction line 10 에서 'Incorrect argument at position 45'
파싱 에러 발생. 이유는 보기 좋게 맞추려고 컬럼 정렬용으로 연속 공백을
넣었는데 MC 26.1.2 의 명령 파서가 컬럼 정렬을 토큰 구분으로 처리하지
못해 다음 인자를 못 찾음.

btn.mcfunction 의 가운데 타일 summon 라인 (~0.5   ~0.375 같이 두 칸
띄운 곳들) 도 동일 패턴 → 한 칸 공백으로 정규화.
2026-05-18 21:27:04 +09:00
Claude (owner)
75daf5bab9 music_quiz: 버튼 비활성 분기에서 data modify entity 단일 대상 제약 회피
interaction 이 버튼당 3개가 된 이후 (v1.0.16+) `data modify entity
@e[...] response set value 0b` 가 다중 대상 에러로 실패. 어차피 비활성
시점에 버튼 블록은 air 로 바꾸므로 interaction 과 text_display 도 함께
kill 하는 게 일관적 — 라벨이 stale 로 남는 것도 방지.
2026-05-17 04:17:58 +09:00
Claude (owner)
ba7089e7b2 music_quiz: 버튼 머리 hitbox 정확한 면 정합 + 라벨 text_display 자동 부착
오프셋 부호 정정 (v1.0.16 까지 모든 버전이 잘못된 convention 사용):
stone_button[face=wall, facing=X] AABB 는 facing X 쪽 face 에 붙어 안쪽
1/8 만 채움. 따라서 머리 hitbox center 는:
  south  z = 0.0625   (이전 -0.0625)
  north  z = 0.9375   (이전  1.0625)
  east   x = 0.0625   (이전 -0.0625)
  west   x = 0.9375   (이전  1.0625)

이전 부호는 머리 face 의 normal 방향 쪽으로 머리가 "튀어나온다" 라
가정했는데 실제로는 face 의 안쪽 1/8 로 들어가는 구조였음. 그 결과
v1.0.16 의 width=0.125 interaction 은 머리와 겹치지 않고 옆에 떠
있어 (touch only at edge) 클릭이 안 됨.

라벨 text_display 추가 (사용자 요청):
- button_defs 항목에 optional label, label_color, label_font, label_scale
  추가. 색 기본 black, 폰트 기본 minecraft:default, 크기 기본 1.0.
- handler 가 btn_prep 로 기본값 채운 뒤 btn 호출 — 매크로 인자 미존재
  에러 회피.
- btn 안에서 facing 별 위치/yaw 로 text_display 1개 summon. 같은 벽
  (button 머리 반대편 블록) 의 visible 면에 ~0.01 띄워 부착, y-1.
- background:0 (투명) 으로 벽에 직접 새긴 느낌.
- label 이 빈 문자열이면 summon 스킵.

commands/stop 에 stale text_display 정리도 추가 — 옛 정의 이름으로
남은 라벨이 reload 후에도 지워지지 않는 문제 방지.
2026-05-17 04:14:39 +09:00
Claude (owner)
55ab7fc04b music_quiz: 버튼 interaction hitbox 를 3 타일 분할로 정확히 정합
단일 interaction 의 horizontal hitbox 는 width × width 정사각형 강제라
"가로 0.375 × 두께 0.125" 직사각형이 불가능 → 두께 방향만 잡으면
가로 방향이 짧아지고, 가로를 잡으면 두께가 0.375 가 되어 벽 안쪽으로
0.25 묻혀 F3+B 디버그에서 wireframe 이 벽 안과 머리 앞쪽으로 튀어나옴.

해결: width=0.125 interaction 3개를 가로축으로 타일링.
- 각 타일 깊이 0.125 = 버튼 머리 두께와 정확히 일치
- 3 × 0.125 = 0.375 = 버튼 머리 가로와 정확히 일치 (gap 없이 인접)
- facing 별 가로축이 다름: south/north 는 x, east/west 는 z

selector limit=1 제거 — 한 버튼에 interaction 이 3개라 limit=1 두면
MC 가 임의로 1개만 골라 잘못된 entity 만 검사할 수 있음. `on target`
은 클릭된 1개만 통과하므로 limit 없이도 단일 발화가 보장됨.
2026-05-17 04:05:01 +09:00
Claude (owner)
28f1012294 music_quiz: 버튼 interaction 오프셋을 facing 으로부터 자동 계산
button_defs 항목은 이제 {n, x, y, z, f, c} 만 가진다 (ox/oy/oz/w/h 제거).
사용자가 facing 을 바꿀 때마다 손으로 오프셋 표를 옮겨 적을 필요가 없도록
repeat/buttons/btn.mcfunction 한 곳에 facing → 소환 오프셋을 고정해두고
mq:tmp.btn{f:"..."} 분기로 디스패치한다. width/height 도 stone_button
hitbox 에 맞춰 0.375/0.25 로 박제.

handler 는 각 entry 를 mq:tmp.btn 으로 복사한 뒤 btn 을 호출해 분기
predicate 에 사용할 수 있게 한다.
2026-05-17 03:56:41 +09:00
Claude (owner)
4349fddc25 music_quiz: dialog tag entry 에 required:false 추가 — /reload 시 datapack 깨짐 방지
증상:
- /reload 후 다음 에러:
  - Couldn't load tag minecraft:quick_actions as it is missing following references: mq:answer
  - Failed to load function mq:commands/start (Can't find element 'mq:page1' in registry 'minecraft:dialog')
  - Failed to load function mq:answer/open (Can't find element 'mq:answer' ...)

원인:
- MC 의 minecraft:dialog 레지스트리는 hot-reload 불가. /reload 로는 dialog 파일이
  registry 에 등록되지 않음 — 서버 완전 재시작이 필요.
- 그 상태에서 tag 가 `["mq:answer"]` 같은 짧은 형식으로 dialog 를 참조하면
  MC 는 누락된 reference 로 보고 태그 로드 실패 → 같은 reload 의 function 들이
  dialog registry 를 못 찾아 연쇄 실패.

수정:
- quick_actions.json: `"mq:answer"` → `{ "id": "mq:answer", "required": false }`.
  required:false 는 reference 가 없을 때 silently 무시하라는 지시. /reload 직후
  잠시 dialog registry 가 비어있어도 datapack 자체가 깨지지 않음.

주의 (운영):
- 데이터팩 업데이트 후에는 반드시 서버를 완전 재시작 해야 dialog show 호출이
  정상 동작함. /reload 만으로는 dialog 파일이 registry 에 들어가지 않음 — 이건
  MC 26.1 시점의 dialog 시스템 자체 제약 (Smithed/Mojang 문서 기준).
2026-05-17 00:10:51 +09:00
Claude (owner)
a8d09ece02 music_quiz: marker 엔티티 제거 + 노래 재생 채널을 player 로 변경
## marker 제거
모든 marker 소환 코드는 write-only — 어디에서도 @e[type=marker] / tag=default
selector 로 읽거나 죽이지 않았음. interaction 엔티티가 클릭 UI 를 대체한 이후
완전히 쓸모없는 잔존물.

- 삭제: quiz/macro/summon.mcfunction, quiz/macro/summon2.mcfunction (소비처 없음)
- commands/stop.mcfunction: marker_call 빌드 + macro 호출 제거.
  기존 월드에 누적된 legacy marker 청소를 위해 `kill @e[type=minecraft:marker,tag=mq]`
  한 줄 추가 (tag=mq 스코프라 외부 마커는 건드리지 않음).
- quiz/setanswer.mcfunction: 정답 marker 소환 블록 제거.
- init/config.mcfunction: marker 좌표 템플릿 (mq:main marker) 제거.

`answer.title="음악퀴즈"` 대기상태 sentinel 은 marker 외에 reader 가 없지만
다른 reset 의미를 가질 가능성을 고려해 보수적으로 유지.

## 노래 재생 채널을 player 로
init/config.mcfunction: mq:main audio.source 를 "weather" → "player".
play_sound / stop_sound 매크로 모두 동일 source 값을 읽으므로 한 곳 변경으로
모든 노래 재생/정지 채널이 player 채널로 이동. 음악/마스터/플레이어 슬라이더
중 "player" (음성) 슬라이더로 노래 음량 제어 가능.

타이머 비프 / UI 클릭음 등은 "노래 재생" 이 아니므로 weather 채널 그대로 유지.
2026-05-16 23:44:00 +09:00
Claude (owner)
b43d120e66 music_quiz: pack.mcmeta 를 min_format/max_format [101,1] 로 갱신 + normalize/step.mcfunction set string from 파싱 에러 수정
- pack.mcmeta: 25w31a 이후 도입된 min_format/max_format 배열 스펙으로 교체. min_format >= 82 이므로 pack_format 키는 생략.
- step.mcfunction L8, L45: `data modify ... set string from <source>` 는 잘못된 문법. 올바른 형태는 `set string <source> [start] [end]` (from 없음). 이로 인해 8행 부근 파싱이 멈추던 문제 해결.
2026-05-16 23:36:03 +09:00
claude
f0a2e4fb6b music_quiz: [정답 입력] 채팅 메시지 제거 + 외부 모드 설치 검증 가드 추가
- quiz/setanswer.mcfunction: 클릭형 tellraw 제거. dialog/`/trigger input` 인프라는 유지하여 모드 없는 환경 fallback.
- 외부 모드 검증 (load/login/start):
  - mq_chat_mod: 서버 전용 모드 (mc_chat_answer_mod) — `#server mq_chat_mod` fake holder 로 서버 presence 검증.
  - mq_video_mod: 클라이언트 렌더링 모드 (mc_video_player_mod) — 같은 objective 안에 `#server` (서버 컴포넌트 매 tick 갱신) + `<player>` (클라 payload 수신 시 갱신) 두 holder 로 server/client 부재 안내 분리.
  - start.mcfunction: server presence 우선 검사 → per-player client presence 검사. unset 매치 안 되는 selector 이슈는 `add @a ... 0` 으로 materialize.
  - login.mcfunction: 플레이어 join 시 `mq_video_mod=0` 초기화 (stale 1 방지).
- docs/mc_video_player_mod_integration.md: video mod 측 구현 사양 (서버 컴포넌트 매 tick presence pulse + client payload handshake, 주기 재전송 필수 명시).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 22:29:13 +09:00
Claude (owner)
2f6dc17092 music_quiz: interaction hitbox 를 stone_button 면 크기에 정합
interaction entity 의 위치/크기를 facing 별 오프셋 + 버튼 hitbox
치수 (6/16 × 4/16) 에 맞춰 분리.

- button_defs 각 항목에 ox/oy/oz/w/h 추가. facing 별 보정값으로
  interaction 의 "튀어나온 쪽 면 = 버튼 visible face" 가 되게 함.
  반대편은 벽 블록 속으로 들어가 invisible.
- width=0.375f, height=0.25f → 가로/세로 버튼 face 와 정합.
  horizontal hitbox 가 square 강제라 두께는 0.375 까지 가지만,
  벽 쪽 0.25 가 wall 블록 안에 묻혀 시각적으로는 버튼 두께 0.125 만 튀어나옴.
- btn.mcfunction 의 summon 라인이 매크로 변수 (~$(ox)/$(oy)/$(oz)) 와
  $(w)f / $(h)f 사용으로 변경됨.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:23:30 +09:00
Claude (owner)
c39a0516bc music_quiz: interaction 엔티티 소환을 데이터팩 내부로 흡수
월드 cmd block 의존 (redstone_block/red_wool 펄스) 을 제거하고
btn.mcfunction 이 직접 summon 하도록 변경.

- buttons=-1 초기화 단계에서 기존 mq/<버튼명> interaction 을 모두
  kill 후 정확히 1개를 (x+0.5, y, z+0.5) 에 1f×1f 로 재소환.
  /reload 마다 dup 누적 없이 "버튼당 1개, 올바른 좌표" 로 수렴.
- /reload → load → commands/stop 이 buttons 점수를 -1 로 재설정 →
  다음 tick 에 ensure 로직 실행. /kill @e 후에도 /reload 한 번으로 복구.
- stone_button 직접 감지 fallback 및 잉여 state machine (1→2→0)
  제거. 클릭 경로는 interaction 단일화 → trigger 투표 흐름 보존.
- README 의 버튼 본체 설명을 새 구조로 갱신.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 21:07:31 +09:00
87 changed files with 378 additions and 756 deletions

141
README.md
View File

@@ -16,39 +16,20 @@
### 호환 버전 ### 호환 버전
- **Minecraft 26.1.2** (`pack.mcmeta``min_format`/`max_format` 모두 - **Minecraft 26.1.2** (pack_format `75`) 기준.
`[101, 1]`).
- 1.21.6에서 도입된 `dialog` 시스템, 1.21+의 단수형 `function/` 태그 폴더, - 1.21.6에서 도입된 `dialog` 시스템, 1.21+의 단수형 `function/` 태그 폴더,
매크로 함수(`function ... with storage`) 기능을 사용한다. 매크로 함수(`function ... with storage`) 기능을 사용한다.
- 텍스트 컴포넌트는 JSON 표기로 작성돼 있으며, 1.21.5 이후의 SNBT 파서와도 - 텍스트 컴포넌트는 JSON 표기로 작성돼 있으며, 1.21.5 이후의 SNBT 파서와도
호환된다 (JSON은 SNBT의 부분집합). 호환된다 (JSON은 SNBT의 부분집합).
### 외부 모드 의존성 (서버/클라) ### 100% 바닐라 — 의존 플러그인 없음
서버 측 검증이 들어가 있어 다음 두 모드가 반드시 깔려 있어야 `/start`
진행된다. 미설치 시 `commands/start` 의 게이트가 사유와 함께 차단한다.
- **`mc_chat_answer_mod` v1.3.7+** — 서버 전용. 채팅으로 정답 입력을
받는다. presence 는 `#server mq_chat_mod` 점수로 매 tick 표시되며,
`SERVER_STARTED` / `END_DATA_PACK_RELOAD` / `PlayerJoin` / `ServerTick`
네 지점에서 갱신된다.
https://git.tkrmagid.kr/tkrmagid/mc_chat_answer_mod/releases/tag/v1.3.7
- **`mc_video_player_mod`** — 클라이언트 + 서버 모두 필요. 서버 컴포넌트가
`#server mq_video_mod` 를 1 로 갱신하고, 클라 join handshake 가 도착하면
`<player> mq_video_mod` 를 1 로 set. `/start` 는 서버 부재 시 단일 차단,
특정 플레이어 부재 시 본인에게 안내 + 게임 시작 차단.
### 100% 바닐라 — 의존 플러그인 없음 (모드 외)
음원 재생과 정답 이미지 표시는 모두 바닐라 명령으로 처리한다. 음원과 음원 재생과 정답 이미지 표시는 모두 바닐라 명령으로 처리한다. 음원과
페인팅 텍스처는 [minecraft_launcher](https://git.tkrmagid.kr/tkrmagid/minecraft_launcher) 페인팅 텍스처는 [minecraft_launcher](https://git.tkrmagid.kr/tkrmagid/minecraft_launcher)
가 만들어주는 리소스팩(`musicquiz` 네임스페이스)에서 가져온다. 가 만들어주는 리소스팩(`musicquiz` 네임스페이스)에서 가져온다.
- 음원: `/playsound musicquiz:track_NN <source> @s ~ ~ ~ <volume> <pitch>` - 음원: `/playsound musicquiz:track_NN <source> @s ~ ~ ~ <volume> <pitch>`
(예: `musicquiz:track_01`). `init/config.mcfunction` `audio.source` (예: `musicquiz:track_01`). 채널은 기본 `weather` `stopsound` 와 함께 묶여 있다.
채널을 결정하며, **곡 재생은 기본 `player` 채널** (음성/플레이어 볼륨
슬라이더로 음량 제어). `stopsound` 도 같은 채널로 묶여 있다. 카운트다운
비프와 종료 클릭 같은 UI 사운드는 별도로 `weather` 채널을 사용한다.
- 정답 이미지: `painting_variant musicquiz:cover_NN``/summon painting` 으로 - 정답 이미지: `painting_variant musicquiz:cover_NN``/summon painting` 으로
벽에 띄우고, 다음 곡 직전 `kill @e[type=painting,tag=mq_cover]` 로 제거. 벽에 띄우고, 다음 곡 직전 `kill @e[type=painting,tag=mq_cover]` 로 제거.
@@ -90,44 +71,29 @@
### 입력 버튼 ### 입력 버튼
관리자가 사용하는 6개의 물리 스톤 버튼. 좌표·표면 방향·실행 명령·라벨 관리자가 사용하는 6개의 물리 스톤 버튼. 좌표·표면 방향·실행 명령은
`mq:init/buttons`에서 storage 리스트(`mq:main button_defs`)로 관리되며, `mq:init/buttons`에서 storage 리스트(`mq:main button_defs`)로 관리되며,
`mq:repeat/buttons/handler`가 매 틱 storage 인덱스로 `btn_prep` `mq:repeat/buttons/handler`가 매 틱 storage 인덱스로 `btn` 매크로를 호출한다.
`btn` 매크로 체인을 호출한다.
- `start` / `stop` / `skip` / `hint` / `replay` / `test` - `start` / `stop` / `skip` / `hint` / `replay` / `test`
버튼 본체는 보이는 `stone_button` 블록 + 클릭을 받는 `interaction` 엔티티 버튼 본체는 보이는 `stone_button` 블록 + 그 좌표에 덮인 `interaction`
3 타일 (블록 면 바깥, 플레이어 쪽으로 살짝 튀어나오게) + 버튼 바로 아래 엔티티로 구성된다. 클릭 처리는 항상 `interaction` 경로로 흐르므로
벽면에 부착되는 `text_display` 라벨 1 개로 구성된다. interaction 폭이 `on target as @s` 로 누른 플레이어가 식별되고, 다수결(`trigger $(n)`)
`width × width` 정사각형으로 강제되기 때문에 stone_button hitbox 의 가로 투표가 성립한다.
0.375 를 0.125 폭 × 3 타일로 덮고, 깊이는 두께(0.125) 만큼 밖으로 밀어
vanilla stone_button 클릭이 동시에 발화되지 않도록 한다. 라벨은 `bold`
적용 text component 로 직접 렌더링된다.
`button_defs` 항목의 필드: `interaction` 은 데이터팩이 직접 소환·관리한다 — `buttons` 점수가
`-1` (초기화) 일 때마다 같은 태그의 기존 entity 를 정리하고 정확히
- 필수: `n` (이름·태그), `x,y,z`, `f` (facing), `c` (실행 명령) 1개를 (재)소환한다. `/reload``commands/stop` 을 호출해 `buttons`
- 옵션: `label`, `label_color` (기본 `black`), `label_font` (기본 점수를 `-1` 로 재설정하므로, 리로드 시 자동 보장된다. `/kill @e`
`minecraft:default`), `label_scale` (기본 `1.0`). `btn_prep` 에서 지워졌어도 다음 `/reload` 한 번으로 복구. 월드 회로(커맨드블럭) 의존은
defaults + `merge from` 패턴으로 기본값이 자동 채워진다. 다.
클릭 처리는 항상 `interaction` 경로로 흐르므로 `on target as @s` 로 누른
플레이어가 식별되고, 다수결(`trigger $(n)`) 투표가 성립한다.
`interaction` / `text_display` 는 데이터팩이 직접 소환·관리한다 —
`buttons` 점수가 `-1` (초기화) 일 때마다 같은 태그의 기존 entity 를
정리하고 정확한 개수만 (재)소환한다. `/reload``commands/stop`
호출해 `buttons` 점수를 `-1` 로 재설정하므로 리로드 시 자동 보장된다.
`/kill @e` 로 지워졌어도 다음 `/reload` 한 번으로 복구. 월드 회로
(커맨드블럭) 의존은 없다.
### 파일 구조 ### 파일 구조
``` ```
music_quiz/ music_quiz/
├── pack.mcmeta # min_format/max_format [101, 1] ├── pack.mcmeta # pack_format 75
├── pack.png
└── data/ └── data/
├── minecraft/tags/function/ ├── minecraft/tags/function/
│ ├── load.json # → mq:load │ ├── load.json # → mq:load
@@ -140,9 +106,9 @@ music_quiz/
│ ├── tick.mcfunction # 매 틱 서브함수 디스패치 (init 게이팅) │ ├── tick.mcfunction # 매 틱 서브함수 디스패치 (init 게이팅)
│ ├── tellraw.mcfunction # 매크로 prefix 메시지 헬퍼 │ ├── tellraw.mcfunction # 매크로 prefix 메시지 헬퍼
│ ├── init/ # 사용자 설정·정적 데이터 (수정 포인트) │ ├── init/ # 사용자 설정·정적 데이터 (수정 포인트)
│ │ ├── config.mcfunction # 주제·스폰·오디오·페인팅 설정 │ │ ├── config.mcfunction # 주제·스폰·오디오·페인팅·marker 설정
│ │ ├── songs.mcfunction # 곡 목록 + max_index 자동계산 │ │ ├── songs.mcfunction # 곡 목록 + max_index 자동계산
│ │ ├── buttons.mcfunction # 버튼 좌표·실행 명령·라벨 │ │ ├── buttons.mcfunction # 버튼 좌표·실행 명령
│ │ └── triggers.mcfunction # 다수결 트리거 정의 │ │ └── triggers.mcfunction # 다수결 트리거 정의
│ ├── commands/ # start·stop·skip·hint·replay·test │ ├── commands/ # start·stop·skip·hint·replay·test
│ ├── quiz/ # 게임 진행 로직 │ ├── quiz/ # 게임 진행 로직
@@ -151,20 +117,18 @@ music_quiz/
│ │ └── macro/ # 매크로 진입점 │ │ └── macro/ # 매크로 진입점
│ │ ├── setanswer.mcfunction # songs[$(idx)] → answer + track/cover id │ │ ├── setanswer.mcfunction # songs[$(idx)] → answer + track/cover id
│ │ ├── play_sound.mcfunction # $playsound 매크로 │ │ ├── play_sound.mcfunction # $playsound 매크로
│ │ ── stop_sound.mcfunction # $stopsound 매크로 │ │ ── stop_sound.mcfunction # $stopsound 매크로
│ │ └── summon{,2}.mcfunction # 정답 marker 엔티티 + alias 체인
│ ├── images/ # 정답 페인팅 표시·제거 │ ├── images/ # 정답 페인팅 표시·제거
│ │ ├── show.mcfunction # cover painting 소환 │ │ ├── show.mcfunction # cover painting 소환
│ │ ├── clear.mcfunction # cover painting 일괄 제거 │ │ ├── clear.mcfunction # cover painting 일괄 제거
│ │ └── macro/show.mcfunction # $summon painting 매크로 │ │ └── macro/show.mcfunction # $summon painting 매크로
│ ├── repeat/ # tick에서 호출되는 매 틱 처리 │ ├── repeat/ # tick에서 호출되는 매 틱 처리
│ │ ├── players·check_answer·timer.mcfunction │ │ ├── players·check_answer·timer.mcfunction
│ │ ├── timers/{init2,init6,init10}.mcfunction # init 단계별 timer 디스패치 분할 │ │ ├── buttons/{handler,btn}.mcfunction
│ │ ├── buttons/{handler,btn_prep,btn}.mcfunction
│ │ └── triggers/{handler,trigger}.mcfunction │ │ └── triggers/{handler,trigger}.mcfunction
── answer/ # 채팅 정답 입력 정규화/판정 (chat_answer 모드 경로) ── players/login.mcfunction
│ └── players/{login,mod_active_notice}.mcfunction
├── dialog/page{1,2,3}.json ├── dialog/page{1,2,3}.json
├── painting_variant/{cover_01..50,gif}.json
└── advancement/player/login.json └── advancement/player/login.json
``` ```
@@ -186,24 +150,21 @@ music_quiz/
- `title`, `max_index`, `spawn` — 설정 - `title`, `max_index`, `spawn` — 설정
- `audio` = `{namespace, source, volume, pitch}``/playsound` 파라미터 - `audio` = `{namespace, source, volume, pitch}``/playsound` 파라미터
- `image` = `{namespace, x, y, z, facing}` — 정답 페인팅 좌표 - `image` = `{namespace, x, y, z, facing}` — 정답 페인팅 좌표
- `marker` = `{x, y, z}` — 정답 입력 marker 엔티티 위치
- `answer` = `{title, author, alias, track, cover}` — 현재 곡 정답 - `answer` = `{title, author, alias, track, cover}` — 현재 곡 정답
- `songs` — 곡 목록 (`mq:init/songs` 가 채움) - `songs` — 곡 목록 (`mq:init/songs` 가 채움)
- `button_defs` / `trigger_defs` — 버튼·트리거 정의 - `button_defs` / `trigger_defs` — 버튼·트리거 정의
- `mq:tmp` — setanswer·play_sound·페인팅·버튼 호출용 임시 페이로드 (idx, pad, num, playsound, painting, btn, btn_default) - `mq:tmp` — setanswer·play_sound·페인팅 호출용 임시 페이로드 (idx, pad, num, playsound, painting, marker_call)
- `mq:input` — 채팅 정답 입력 큐 (chat_answer 모드 경로)
- `func:temp``func:` 헬퍼 함수용 임시 NBT - `func:temp``func:` 헬퍼 함수용 임시 NBT
> 참고: 과거 `marker` 스토리지와 `minecraft:marker` 정답 입력 엔티티는
> 폐기됨. `commands/stop` 의 `kill @e[type=marker,tag=mq]` 한 줄만 이전
> 월드에 남아 있을 수 있는 legacy entity 청소 목적으로 유지된다.
### 설정 (한 곳에서 수정) ### 설정 (한 곳에서 수정)
세계마다 다른 값은 모두 `data/mq/function/init/` 폴더에서 편집한다. 세계마다 다른 값은 모두 `data/mq/function/init/` 폴더에서 편집한다.
`/reload` 후 반영된다. `/reload` 후 반영된다.
- **`init/config.mcfunction`** — 주제, 스폰 위치, 오디오 설정(`audio`), - **`init/config.mcfunction`** — 주제, 스폰 위치, 오디오 설정(`audio`),
정답 페인팅 좌표(`image`). `title``[ … ]` 채팅 접두사로도 사용된다. 정답 페인팅 좌표(`image`), marker 엔티티 좌표. `title``[ … ]`
채팅 접두사로도 사용된다.
- **`init/songs.mcfunction`** — 곡 목록 (한 줄에 한 곡씩 append). `alias` - **`init/songs.mcfunction`** — 곡 목록 (한 줄에 한 곡씩 append). `alias`
배열의 문자열은 정답 판정 시 동의어로 인정된다. **곡의 순서가 리소스팩 배열의 문자열은 정답 판정 시 동의어로 인정된다. **곡의 순서가 리소스팩
트랙 번호와 1:1 매칭** 되므로 순서 변경 시 리소스팩도 함께 재생성해야 트랙 번호와 1:1 매칭** 되므로 순서 변경 시 리소스팩도 함께 재생성해야
@@ -222,59 +183,31 @@ JSON 텍스트 컴포넌트가 storage 참조를 일관되게 지원하지 않
### 설치 ### 설치
1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사. 1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사.
2. 서버 mods 폴더에 `mc_chat_answer_mod` v1.3.7+ 와 `mc_video_player_mod` 2. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용
jar 설치. `mc_video_player_mod` 는 클라이언트 측에도 설치 필요.
3. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용
(런처가 자동 처리). (런처가 자동 처리).
4. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상. 3. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상.
5. `mq:init/buttons` 에 정의된 좌표 부근에 6개 버튼이 자동 배치된다. 4. 좌표 `144, 62, -225` 부근에 6개 버튼이 자동 배치된다.
6. `start` 버튼을 눌러 게임 시작 — 모드 미설치 시 사유와 함께 차단된다. 5. `start` 버튼을 눌러 게임 시작.
### 좌표 의존성 (주의) ### 좌표 의존성 (주의)
다음 좌표 데이터팩 안에 박혀 있어 다른 월드에서 쓰려면 직접 바꿔야 다음 좌표 데이터팩 안에 박혀 있어, 다른 월드에서 그대로 사용하려면
한다. 현재 박혀 있는 좌표는 본인 월드 기준이므로 그대로 옮겨가면 동작 `init/config.mcfunction` 의 값을 바꿔야 한다:
안 한다.
- 정답 페인팅 / 플레이어 스폰 — `init/config.mcfunction` (`image`, `spawn`) - 정답 입력 marker: `144 59 -219` `marker.{x,y,z}`
- 버튼 좌표·facing — `init/buttons.mcfunction` (`button_defs``x,y,z,f`) - 정답 페인팅: `144 84 -261` (facing south) — `image.{x,y,z,facing}`
- 플레이어 스폰: `144 61 -219` (yaw 180) — `spawn`
- 버튼 좌표: `140..148, 62, -225` / `144, 62, -213``mq:init/buttons`
--- ---
## 변경 이력 ## 변경 이력
### 2026-05-19 — v1.0.25: 버튼 hitbox/라벨 미세조정 + 곡목록·좌표 갱신
- `repeat/buttons/btn.mcfunction`: interaction hitbox 미세조정 (`width`
0.13 가운데 타일로 micro-gap 보정, `height` 0.26, 깊이 오프셋
0.07/0.93, text_display Y `~-0.5` 로 라벨 위치 조정).
- 셀렉터 정렬 `[type=...,tag=mq,tag=$(n)]` → `[distance=0..,tag=mq,
tag=$(n),type=...]`.
- `init/songs.mcfunction`: 아이유 17 곡 셋으로 교체 (alias 빈 배열).
- `init/buttons.mcfunction`: 버튼 좌표 본인 월드 기준으로 갱신,
`label` 필드 추가 ("게임시작" / "정지" / "넘기기" / "힌트" /
"다시듣기" / "소리 테스트").
- `repeat/timer.mcfunction` 분할 → `repeat/timers/{init2,init6,init10}`.
- `commands/start.mcfunction` 에 `mq_video_mod` 게이트 추가 (서버 부재
단일 차단 + 플레이어별 부재 안내). `load.mcfunction` 에 `mq_video_mod`
objective + `#server` 0 materialize 추가.
### 2026-05-18 ~ 19 — v1.0.19 ~ v1.0.24: 버튼 인프라 안정화
- v1.0.19/20/21: `btn_prep` defaults+merge 패턴, `positioned $(x).0`
로 +0.5 보정 회피, interaction 3 타일 분할, `text_display` 도입.
- v1.0.23: 채팅정답 모드 false negative 의 진짜 fix — 데이터팩 게이트는
유지하고 모드 (`mc_chat_answer_mod` v1.3.7) 의 presence pulse 를
4 지점으로 확장.
- v1.0.24: `text_display` Y 보정 (`~-1` → `~-0.25`) + 라벨 bold + v1.0.21
의 interaction 깊이 부호 반전 수정.
### 2026-05-13 — 26.1.2 호환 + 1차 정리 (`b1babad`) ### 2026-05-13 — 26.1.2 호환 + 1차 정리 (`b1babad`)
이전 푸시본(`6841b7a 이전퀴즈 데이터팩`)을 26.1.2 기준으로 정비. 이전 푸시본(`6841b7a 이전퀴즈 데이터팩`)을 26.1.2 기준으로 정비.
- `pack_format` 69 → 75 (MC 26.1.2 / 1.21.11). 이후 `min_format`/ - `pack_format` 69 → 75 (MC 26.1.2 / 1.21.11)
`max_format` 가 `[101, 1]` 로 갱신됨 (현재).
- `mq:load`, `mq:players/login`, `mq:commands/start`, `mq:commands/stop`, - `mq:load`, `mq:players/login`, `mq:commands/start`, `mq:commands/stop`,
`mq:quiz/start`, `mq:quiz/end`, `mq:repeat/buttons/btn` 등에 남아 있던 `mq:quiz/start`, `mq:quiz/end`, `mq:repeat/buttons/btn` 등에 남아 있던
`# say ...` / `# stopsound` 사문화 디버그 주석 제거 `# say ...` / `# stopsound` 사문화 디버그 주석 제거

View File

@@ -3,7 +3,6 @@ data modify storage func:temp zero set value 0
$data modify storage func:temp length set value $(length) $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 store result score result func.temp run function func:comp_num {n1:"zero",n2:"length"}
# warn-off execute-group
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 tellraw @s {"text":"length는 1이상 이어야 합니다.","color":"red"}
execute if score result func.temp matches 1 run return 0 execute if score result func.temp matches 1 run return 0

View File

@@ -5,7 +5,7 @@ execute if score length func.temp matches 0 run return 1
execute store result score random func.temp run random value 0..2147483646 execute store result score random func.temp run random value 0..2147483646
scoreboard players operation random func.temp %= length func.temp scoreboard players operation random func.temp %= length func.temp
function func:shuffle/f2 with storage func:temp {index:0} 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 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 function func:shuffle/f2 with storage func:temp shuffle

View File

@@ -3,7 +3,6 @@ data modify storage func:temp zero set value 0
$data modify storage func:temp length set value $(length) $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 store result score result func.temp run function func:comp_num {n1:"zero",n2:"length"}
# warn-off execute-group
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 tellraw @s {"text":"length는 1이상 이어야 합니다.","color":"red"}
execute if score result func.temp matches 1 run return 0 execute if score result func.temp matches 1 run return 0

View File

@@ -3,7 +3,6 @@ execute store result score result func.temp run function func:is_space with stor
$execute store result score result2 func.temp run function func:is_index {l1:"half",l2:"result",index:$(index)} $execute store result score result2 func.temp run function func:is_index {l1:"half",l2:"result",index:$(index)}
# warn-off execute-group
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 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 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 execute if score result2 func.temp matches 1 run data modify storage func:temp text_list append from storage func:temp space.text

View File

@@ -9,7 +9,6 @@ data modify storage mq:tmp judge.answer set from storage mq:tmp norm.acc
function mq:answer/macro/match with storage mq:tmp judge function mq:answer/macro/match with storage mq:tmp judge
# 2) 제목 매치 실패 시 alias 들과 순차 비교 (조기 종료) # 2) 제목 매치 실패 시 alias 들과 순차 비교 (조기 종료)
# warn-off execute-group
execute unless score @s answer matches 1 run data modify storage mq:tmp aliases set from storage mq:main answer.alias execute unless score @s answer matches 1 run data modify storage mq:tmp aliases set from storage mq:main answer.alias
execute unless score @s answer matches 1 run function mq:answer/iter_aliases execute unless score @s answer matches 1 run function mq:answer/iter_aliases

View File

@@ -5,7 +5,6 @@ execute if score init main matches 1..4 run return run function mq:tellraw {"tex
execute if score init main matches 6.. 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 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'}
# warn-off execute-group
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.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 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 function func:hint with storage mq:main hint

View File

@@ -5,6 +5,5 @@ execute if score init main matches 1..4 run return run function mq:tellraw {"tex
execute if score init main matches 6.. 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 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'}
# warn-off execute-group
execute if score init main matches 5 run function mq:quiz/stop_sound 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 execute if score init main matches 5 run function mq:quiz/play_sound

View File

@@ -5,6 +5,5 @@ execute if score init main matches 1..4 run return run function mq:tellraw {"tex
execute if score init main matches 6.. 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 10 run return run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"red",msg:'""'}
# warn-off execute-group
execute if score init main matches 5 run scoreboard players set skip buttons -2 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 execute if score init main matches 5 run function mq:quiz/correct

View File

@@ -64,13 +64,13 @@ bossbar set mq:process players @a
data modify storage mq:main answer set value {title:"음악퀴즈", alias:[]} data modify storage mq:main answer set value {title:"음악퀴즈", alias:[]}
# 이전 버전이 남긴 legacy marker 정리 (현재는 marker 사용 안 함) # 이전 버전이 남긴 legacy marker 정리 (현재는 marker 사용 안 함)
kill @e[distance=0..,tag=mq,type=minecraft:marker] kill @e[type=minecraft:marker,tag=mq]
# 이전 버전이 남긴 잔존 text_display 정리. # 이전 버전이 남긴 잔존 text_display 정리.
# 현재 버튼은 -1 init 단계에서 같은 tag interaction 만 kill 하므로 (n 태그 # 현재 버튼은 -1 init 단계에서 같은 tag interaction 만 kill 하므로 (n 태그
# 가 일치할 때만), 옛 버튼 정의에 있던 이름의 text_display 가 남으면 안 # 가 일치할 때만), 옛 버튼 정의에 있던 이름의 text_display 가 남으면 안
# 지워짐. 여기서 mq 태그 전체를 한 번에 정리해 stale 제거. # 지워짐. 여기서 mq 태그 전체를 한 번에 정리해 stale 제거.
kill @e[distance=0..,tag=mq,type=minecraft:text_display] kill @e[type=minecraft:text_display,tag=mq]
function mq:quiz/stop_sound function mq:quiz/stop_sound
function mq:images/clear function mq:images/clear

View File

@@ -1,6 +1,5 @@
stopsound @a block minecraft:block.stone_button.click_on stopsound @a block minecraft:block.stone_button.click_on
function mq:tellraw {"text":"띵!!!","color":"white","msg":'""'} function mq:tellraw {"text":"띵!!!","color":"white","msg":'""'}
# warn-off execute-group
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 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

View File

@@ -1 +1 @@
kill @e[distance=0..,tag=mq_cover,type=minecraft:painting] kill @e[type=minecraft:painting,tag=mq_cover]

View File

@@ -15,9 +15,9 @@
# repeat/buttons/btn 안에서 한 곳에만 정의되어 있다. # repeat/buttons/btn 안에서 한 곳에만 정의되어 있다.
data modify storage mq:main button_defs set value [] data modify storage mq:main button_defs set value []
data modify storage mq:main button_defs append value {n:"start", x:2773, y:86, z:5968, f:"north", c:"function mq:commands/start with storage mq:main", label:"게임시작"} data modify storage mq:main button_defs append value {n:"start", x:364, y:146, z:-263, f:"east", c:"function mq:commands/start with storage mq:main", label:"게임시작"}
data modify storage mq:main button_defs append value {n:"stop", x:2771, y:86, z:5968, f:"north", c:"function mq:commands/stop with storage mq:main", label:"정지"} data modify storage mq:main button_defs append value {n:"stop", x:364, y:146, z:-265, f:"east", c:"function mq:commands/stop with storage mq:main", label:"정지"}
data modify storage mq:main button_defs append value {n:"skip", x:2769, y:86, z:5968, f:"north", c:"function mq:commands/skip", label:"넘기기"} data modify storage mq:main button_defs append value {n:"skip", x:364, y:146, z:-267, f:"east", c:"function mq:commands/skip", label:"넘기기"}
data modify storage mq:main button_defs append value {n:"hint", x:2767, y:86, z:5968, f:"north", c:"function mq:commands/hint", label:"힌트"} data modify storage mq:main button_defs append value {n:"hint", x:364, y:146, z:-269, f:"east", c:"function mq:commands/hint", label:"힌트"}
data modify storage mq:main button_defs append value {n:"replay", x:2765, y:86, z:5968, f:"north", c:"function mq:commands/replay", label:"다시듣기"} data modify storage mq:main button_defs append value {n:"replay", x:364, y:146, z:-271, f:"east", c:"function mq:commands/replay", label:"다시듣기"}
data modify storage mq:main button_defs append value {n:"test", x:2769, y:87, z:5957, f:"south", c:"function mq:commands/test", label:"소리 테스트"} data modify storage mq:main button_defs append value {n:"test", x:144, y:62, z:-213, f:"north", c:"function mq:commands/test", label:"소리 테스트"}

View File

@@ -2,7 +2,7 @@
data modify storage mq:main title set value "음악퀴즈" data modify storage mq:main title set value "음악퀴즈"
# 플레이어 접속 시 텔레포트 위치 (x y z, r=yaw, f=pitch) # 플레이어 접속 시 텔레포트 위치 (x y z, r=yaw, f=pitch)
data modify storage mq:main spawn set value {x: 2769, y: 85, z: 5963, r: 0, f: 0} data modify storage mq:main spawn set value {x: 144, y: 61, z: -219, r: 180, f: 0}
# 음원 재생 — minecraft_launcher 리소스팩의 musicquiz:track_NN 사운드 이벤트 # 음원 재생 — minecraft_launcher 리소스팩의 musicquiz:track_NN 사운드 이벤트
# namespace — 리소스팩 네임스페이스 (기본 "musicquiz") # namespace — 리소스팩 네임스페이스 (기본 "musicquiz")
@@ -17,6 +17,6 @@ data modify storage mq:main audio set value {namespace: "musicquiz", source: "pl
# namespace — painting_variant 네임스페이스 (기본 "mq") # namespace — painting_variant 네임스페이스 (기본 "mq")
# x,y,z — 페인팅 entity 좌표 (벽면 앞쪽 블록 위치) # x,y,z — 페인팅 entity 좌표 (벽면 앞쪽 블록 위치)
# facing — 페인팅이 바라보는 방향: south=0 / west=1 / north=2 / east=3 # facing — 페인팅이 바라보는 방향: south=0 / west=1 / north=2 / east=3
data modify storage mq:main image set value {namespace: "mq", x: 2775, y: 85, z: 5982, facing: 2b} data modify storage mq:main image set value {namespace: "mq", x: 144, y: 84, z: -261, facing: 0b}
# 곡 개수 max_index 는 init/songs.mcfunction 의 길이로 자동 계산됨 # 곡 개수 max_index 는 init/songs.mcfunction 의 길이로 자동 계산됨

View File

@@ -4,23 +4,56 @@
# 곡 순서가 리소스팩의 track_NN / cover_NN 인덱스와 1:1 매칭된다. # 곡 순서가 리소스팩의 track_NN / cover_NN 인덱스와 1:1 매칭된다.
# 예) {title:"Quiet Song", author:"...", alias:[...], volume:2.0} # 예) {title:"Quiet Song", author:"...", alias:[...], volume:2.0}
data modify storage mq:main songs set value [] data modify storage mq:main songs set value []
data modify storage mq:main songs append value {title:"푸르던", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"Lose My Mind", author:"Don Toliver", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"금요일에 만나요", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"The Chase", author:"Hearts2Hearts", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"나의 옛날이야기", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"HOT SAUCE", author:"BABYMONSTER", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"비밀의 화원", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"Golden", author:"HUNTR/X", alias:[""," "], volume:1.0}
data modify storage mq:main songs append value {title:"겨울잠", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"돌림판", author:"머쉬베놈", alias:["Spin the wheel"], volume:1.0}
data modify storage mq:main songs append value {title:"이런엔딩", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"OVERDRIVE", author:"TWS", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"이름에게", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"눈물참기", author:"QWER", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"드라마", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"깨어", author:"tripleS", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"가을아침", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"like JENNIE", author:"제니", alias:[" "," "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"Rain Drop", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"Rich Man", author:"aespa", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"에필로그", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"I DO ME", author:"KiiiKiii", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"무릎", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"SIGN", author:"izna", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"마음", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"WICKED", author:"ALLDAY PROJECT", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"잠 못 드는 밤 비는 내리고", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"Good Thing", author:"i-dle", alias:["굿 "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"정거장", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"0+0", author:"한로로", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"자장가", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"HANDS UP", author:"MEOVV", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"사랑이 지나가면", author:"아이유", alias:[]} data modify storage mq:main songs append value {title:"Blue Valentine", author:"NMIXX", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"Flower", author:"오반", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"Soda Pop", author:"Saja Boys", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"REBEL HEART", author:"IVE", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"GO!", author:"CORTIS", alias:["","!","GO","","!"], volume:1.0}
data modify storage mq:main songs append value {title:"BEEP", author:"izna", alias:["","",""], volume:1.0}
data modify storage mq:main songs append value {title:"Pookie", author:"FIFTY FIFTY", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"DAISIES", author:"Justin Bieber", alias:["","",""], volume:1.0}
data modify storage mq:main songs append value {title:"빌려온 고양이", author:"ILLIT", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"TOO BAD", author:"OfficialGDRAGON", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"시작의 아이", author:"마크툽", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"STYLE", author:"Hearts2Hearts", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"Good Goodbye", author:"화사", alias:["굿 굿"," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"너에게 닿기를", author:"10CM", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"IRIS OUT", author:"Kenshi Yonezu", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"Sugar On My Tongue", author:"Tyler, The Creator", alias:[" "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"Hollywood Action", author:"BOYNEXTDOOR", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"SPAGHETTI", author:"LE SSERAFIM", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"Gabriela", author:"KATSEYE", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"LIKE YOU BETTER", author:"프로미스나인", alias:[" "," "," "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"뛰어", author:"BLACKPINK", alias:["jump"], volume:1.0}
data modify storage mq:main songs append value {title:"CHANEL", author:"Tyla", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"오늘만 I LOVE YOU", author:"BOYNEXTDOOR", alias:[" "," "], volume:1.0}
data modify storage mq:main songs append value {title:"earthquake", author:"지수", alias:["","",""," "], volume:1.0}
data modify storage mq:main songs append value {title:"윽!", author:"염따", alias:[""], volume:1.0}
data modify storage mq:main songs append value {title:"Abracadabra", author:"Lady Gaga", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"멸종위기사랑", author:"이찬혁", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"Dirty Work", author:"aespa", alias:[" "," "," "," "," "], volume:1.0}
data modify storage mq:main songs append value {title:"HOT", author:"LE SSERAFIM", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"FAMOUS", author:"ALLDAY PROJECT", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {title:"XOXZ", author:"IVE", alias:[" "," ",""], volume:1.0}
data modify storage mq:main songs append value {title:"여름이었다", author:"H1-KEY", alias:[], volume:1.0}
data modify storage mq:main songs append value {title:"LOV3", author:"식케이", alias:["3","3"], volume:1.0}
data modify storage mq:main songs append value {title:"Drive", author:"Ed Sheeran", alias:["",""], volume:1.0}
# 곡 개수는 songs 배열 길이에서 자동 계산됨 # 곡 개수는 songs 배열 길이에서 자동 계산됨
execute store result storage mq:main max_index int 1 run data get storage mq:main songs execute store result storage mq:main max_index int 1 run data get storage mq:main songs

View File

@@ -1,9 +0,0 @@
$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:"정답입력종료"}

View File

@@ -1,6 +0,0 @@
$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

View File

@@ -1,7 +1,5 @@
scoreboard players set timer main 0 scoreboard players set timer main 0
stopsound @a player
execute if score index main >= max_index main run return run function mq:quiz/end with storage mq:main 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 scoreboard players add index main 1

View File

@@ -1,4 +1,3 @@
# warn-off-file always-pass-condition
# 버튼 1개에 대한 매 tick 처리. # 버튼 1개에 대한 매 tick 처리.
# 매크로 인자(mq:tmp.btn): n, x, y, z, f, c, label, label_color, label_font, label_scale # 매크로 인자(mq:tmp.btn): n, x, y, z, f, c, label, label_color, label_font, label_scale
# buttons 점수 상태: # buttons 점수 상태:
@@ -27,78 +26,52 @@
# 인접: 중심 0.375 / 0.5 / 0.625, 각 폭 0.125 → 합 [0.3125, 0.6875]). # 인접: 중심 0.375 / 0.5 / 0.625, 각 폭 0.125 → 합 [0.3125, 0.6875]).
# interaction Y 는 hitbox 바닥 → 소환 y = block y + 0.375, height = 0.25. # interaction Y 는 hitbox 바닥 → 소환 y = block y + 0.375, height = 0.25.
# #
# ---- 깊이축: 블록 면 바로 바깥, 플레이어 쪽 (이중 트리거 방지) ---- # ---- text_display 위치 (버튼 바로 아래 같은 벽면에 부착) ----
# interaction 박스가 stone_button hitbox 와 겹치면 한 번 클릭에 interaction # 같은 벽 (button 의 머리 normal 반대편 블록) 의 visible 면에 살짝 띄워
# 도 발화하고 stone_button 도 vanilla 클릭으로 인식되어 powered=true 애니 # 부착. 텍스트 entity Y 는 텍스트 baseline 근방 → 아래 블록 바닥에 두면
# 메이션이 같이 일어남. → interaction 박스를 버튼 머리 바깥쪽 (플레이어 # 텍스트가 그 블록 안에 위로 솟아남.
# 측) 으로 한 두께 (0.125) 만큼 밀어 ray 가 stone_button 에 닿기 전에 # south : ~0.5 ~-1 ~0.01 yaw 0 (head 가 +z 방향 → 벽 +z=0.01 살짝 띄움)
# interaction 에서 멈추게. # north : ~0.5 ~-1 ~0.99 yaw 180
# # east : ~0.01 ~-1 ~0.5 yaw -90
# 주의: facing 은 "버튼 머리 normal 방향" = 플레이어가 보는 방향. # west : ~0.99 ~-1 ~0.5 yaw 90
# south 면 머리 +z 향함, 벽은 -z 쪽. 따라서 플레이어 쪽 = +z = interaction
# 을 z > 버튼 머리 (0.125) 영역으로. (v1.0.21 에서 한 두께만큼 뺀다는
# 의도였는데 부호를 반대로 잡아 interaction 이 벽 안으로 들어가 있었음.)
# south : 깊이 z 중심 = 0.1875 (interaction z ∈ [0.125, 0.25], 버튼 z ∈ [0, 0.125])
# north : 깊이 z 중심 = 0.8125 (interaction z ∈ [0.75, 0.875], 버튼 z ∈ [0.875, 1])
# east : 깊이 x 중심 = 0.1875
# west : 깊이 x 중심 = 0.8125
#
# ---- positioned 의 .5 보정 회피 ----
# MC 의 vec3 인자는 정수만 쓰면 자동으로 +0.5 보정됨 (블록 중심으로 잡힘).
# positioned 2773 86 5968 → 실제로는 (2773.5, 86, 5968.5). 거기서 ~ 오프셋
# 을 더하면 박스 전체가 0.5 칸 어긋남. $(x).0 $(y).0 $(z).0 처럼 decimal
# 형태로 넘기면 보정 없이 정확한 블록 origin (minimal corner) 이 됨.
#
# ---- text_display 위치 (버튼 바로 아래 같은 벽면에 가운데 정렬) ----
# 버튼 아래 블록의 같은 벽면 (visible 면, 플레이어 쪽) 에 살짝 띄워 부착.
# 가로축: ~0.5 (block 가로 중심, alignment=center 기본값과 합쳐져서 라벨
# 자체도 수평 중앙).
# 세로축: text_display 의 entity Y 는 텍스트 윗변 — 아래로 자람. ~-0.25
# 로 두면 텍스트 윗변이 Y-0.25 (버튼 바로 아래), 한 줄(기본 ~0.5 블록 높이)
# 이 Y-0.75 까지 내려와 버튼 아래 한 칸 벽면 [Y-1, Y] 의 위쪽 절반에
# 자리잡음 — 시각적으로 버튼 바로 밑 가운데 라벨.
# south : ~0.5 ~-0.25 ~0.01 yaw 0 (head 가 +z → 벽면 z=0 에서 +0.01 띄움)
# north : ~0.5 ~-0.25 ~0.99 yaw 180
# east : ~0.01 ~-0.25 ~0.5 yaw -90
# west : ~0.99 ~-0.25 ~0.5 yaw 90
# ---- 비활성: 블록 + interaction × 3 + text_display 전부 제거 후 종료 ---- # ---- 비활성: 블록 + interaction × 3 + text_display 전부 제거 후 종료 ----
# data modify entity @e[...] 는 대상 1개 강제 → interaction 3개 모드에선 # data modify entity @e[...] 는 대상 1개 강제 → interaction 3개 모드에선
# 못 쓰므로 그냥 kill. 어차피 버튼 블록도 air 로 바꾸므로 라벨도 같이 제거. # 못 쓰므로 그냥 kill. 어차피 버튼 블록도 air 로 바꾸므로 라벨도 같이 제거.
$execute if score $(n) buttons matches ..-2 run setblock $(x) $(y) $(z) minecraft:air $execute if score $(n) buttons matches ..-2 run setblock $(x) $(y) $(z) minecraft:air
$execute if score $(n) buttons matches ..-2 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] $execute if score $(n) buttons matches ..-2 run kill @e[type=minecraft:interaction,tag=mq,tag=$(n)]
# $execute if score $(n) buttons matches ..-2 run kill @e[type=minecraft:text_display,tag=mq,tag=$(n)] $execute if score $(n) buttons matches ..-2 run kill @e[type=minecraft:text_display,tag=mq,tag=$(n)]
$execute if score $(n) buttons matches ..-2 run return 0 $execute if score $(n) buttons matches ..-2 run return 0
# ---- 초기화: 블록 + interaction × 3 + text_display 보장 ---- # ---- 초기화: 블록 + interaction × 3 + text_display 보장 ----
$execute unless score $(n) buttons matches -1.. run scoreboard players set $(n) buttons -1 $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 run setblock $(x) $(y) $(z) minecraft:stone_button[face=wall,facing=$(f),powered=false]
$execute if score $(n) buttons matches -1 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] $execute if score $(n) buttons matches -1 run kill @e[type=minecraft:interaction,tag=mq,tag=$(n)]
$execute if score $(n) buttons matches -1 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:text_display] $execute if score $(n) buttons matches -1 run kill @e[type=minecraft:text_display,tag=mq,tag=$(n)]
# south: 깊이축=z(+0.1875, 플레이어 쪽), 가로축=x, 3 타일 + 라벨 # south: 깊이축=z(+0.0625), 가로축=x, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.37 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.375 ~0.375 ~0.0625 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.5 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.5 ~0.375 ~0.0625 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.63 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.625 ~0.375 ~0.0625 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.5 ~-0.5 ~0.01 {Tags:["mq","$(n)"],Rotation:[0f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}} $execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"south"} positioned $(x) $(y) $(z) run summon minecraft:text_display ~0.5 ~-1 ~0.01 {Tags:["mq","$(n)"],Rotation:[0f,0f],background:0,text:'{"text":"$(label)","color":"$(label_color)","font":"$(label_font)"}',transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# north: 깊이축=z(+0.8125, 플레이어 쪽), 가로축=x, 3 타일 + 라벨 # north: 깊이축=z(+0.9375), 가로축=x, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.37 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.375 ~0.375 ~0.9375 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.5 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.5 ~0.375 ~0.9375 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.63 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.625 ~0.375 ~0.9375 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.5 ~-0.5 ~0.99 {Tags:["mq","$(n)"],Rotation:[180f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}} $execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"north"} positioned $(x) $(y) $(z) run summon minecraft:text_display ~0.5 ~-1 ~0.99 {Tags:["mq","$(n)"],Rotation:[180f,0f],background:0,text:'{"text":"$(label)","color":"$(label_color)","font":"$(label_font)"}',transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# east: 깊이축=x(+0.1875, 플레이어 쪽), 가로축=z, 3 타일 + 라벨 # east: 깊이축=x(+0.0625), 가로축=z, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.37 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.0625 ~0.375 ~0.375 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.5 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.0625 ~0.375 ~0.5 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.63 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.0625 ~0.375 ~0.625 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.01 ~-0.5 ~0.5 {Tags:["mq","$(n)"],Rotation:[-90f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}} $execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"east"} positioned $(x) $(y) $(z) run summon minecraft:text_display ~0.01 ~-1 ~0.5 {Tags:["mq","$(n)"],Rotation:[-90f,0f],background:0,text:'{"text":"$(label)","color":"$(label_color)","font":"$(label_font)"}',transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# west: 깊이축=x(+0.8125, 플레이어 쪽), 가로축=z, 3 타일 + 라벨 # west: 깊이축=x(+0.9375), 가로축=z, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.37 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.9375 ~0.375 ~0.375 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.5 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.9375 ~0.375 ~0.5 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.63 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b} $execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x) $(y) $(z) run summon minecraft:interaction ~0.9375 ~0.375 ~0.625 {Tags:["mq","$(n)"],width:0.125f,height:0.25f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.99 ~-0.5 ~0.5 {Tags:["mq","$(n)"],Rotation:[90f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}} $execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"west"} positioned $(x) $(y) $(z) run summon minecraft:text_display ~0.99 ~-1 ~0.5 {Tags:["mq","$(n)"],Rotation:[90f,0f],background:0,text:'{"text":"$(label)","color":"$(label_color)","font":"$(label_font)"}',transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
$execute if score $(n) buttons matches -1 run scoreboard players set $(n) buttons 0 $execute if score $(n) buttons matches -1 run scoreboard players set $(n) buttons 0
@@ -108,10 +81,10 @@ $execute if score $(n) buttons matches -1 run scoreboard players set $(n) button
# 한 버튼에 interaction 3개지만 `on target` 은 클릭된 1개만 통과 # 한 버튼에 interaction 3개지만 `on target` 은 클릭된 1개만 통과
# (나머지는 target 부재로 체인 중단). limit=1 을 두면 MC 가 임의로 1개를 # (나머지는 target 부재로 체인 중단). limit=1 을 두면 MC 가 임의로 1개를
# 골라 잘못된 entity 만 검사하므로 limit 두지 않음. # 골라 잘못된 entity 만 검사하므로 limit 두지 않음.
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 run playsound minecraft:block.stone_button.click_on block @s ~ ~ ~ 1 1 $execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] on target as @s positioned $(x) $(y) $(z) run playsound minecraft:block.stone_button.click_on block @s ~ ~ ~ 1 1
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 if score init main matches 0 run $(c) $execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] on target as @s positioned $(x) $(y) $(z) if score init main matches 0 run $(c)
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 unless score init main matches 0 run trigger $(n) $execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] on target as @s positioned $(x) $(y) $(z) unless score init main matches 0 run trigger $(n)
# ---- 처리 후 attack/interaction NBT 클리어 (다음 tick 중복 발화 방지) ---- # ---- 처리 후 attack/interaction NBT 클리어 (다음 tick 중복 발화 방지) ----
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] 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 attack
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] at @s run data remove entity @s interaction $execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] at @s run data remove entity @s interaction

View File

@@ -6,16 +6,8 @@
# label_color : 기본 "black" # label_color : 기본 "black"
# label_font : 기본 "minecraft:default" # label_font : 기본 "minecraft:default"
# label_scale : 기본 "1.0" (Vector3f 의 한 축, 3축 동일하게 사용됨) # label_scale : 기본 "1.0" (Vector3f 의 한 축, 3축 동일하게 사용됨)
#
# 구현: defaults 컴파운드를 먼저 만들고 entry (mq:tmp.btn) 를 그 위에 merge
# 한 뒤 다시 mq:tmp.btn 으로 되돌린다. data modify ... merge from 은 source
# compound 의 키로 target compound 를 덮어쓰므로 entry 에 있는 값은 보존되고
# entry 에 없는 키만 default 값으로 채워진다.
#
# (이전에 `execute unless data storage mq:tmp btn.label run data modify ...`
# 방식이었으나 MC 26.1.2 parser 가 해당 라인을 거부했음. merge 방식은 문제
# 난 execute-unless-data 구문 자체를 제거.)
data modify storage mq:tmp btn_default set value {label:"",label_color:"black",label_font:"minecraft:default",label_scale:"1.0"} execute unless data storage mq:tmp btn.label run data modify storage mq:tmp btn.label set value ""
data modify storage mq:tmp btn_default merge from storage mq:tmp btn execute unless data storage mq:tmp btn.label_color run data modify storage mq:tmp btn.label_color set value "black"
data modify storage mq:tmp btn set from storage mq:tmp btn_default execute unless data storage mq:tmp btn.label_font run data modify storage mq:tmp btn.label_font set value "minecraft:default"
execute unless data storage mq:tmp btn.label_scale run data modify storage mq:tmp btn.label_scale set value "1.0"

View File

@@ -6,10 +6,44 @@ execute unless score init main matches 2 \
run scoreboard players set timer main 0 run scoreboard players set timer main 0
# start title timer # start title timer
execute if score init main matches 2 run function mq:repeat/timers/init2 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 # next song timer
execute if score init main matches 6 run function mq:repeat/timers/init6 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 # endding timer
execute if score init main matches 10 run function mq:repeat/timers/init10 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

View File

@@ -1,3 +0,0 @@
execute if score timer main matches 300 run title @a title {"text":""}
execute if score timer main matches 290 run function mq:images/clear
execute if score timer main matches 300.. run function mq:quiz/select with storage mq:main

View File

@@ -1,15 +0,0 @@
# warn-off-file execute-group
execute if score timer main matches 20 run title @a title {"text":"3"}
execute 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 timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 40 run title @a title {"text":"2"}
execute 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 timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 60 run title @a title {"text":"1"}
execute 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 timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 100 run title @a title {"text":""}
execute if score timer main matches 100.. run function mq:quiz/select with storage mq:main

View File

@@ -1,15 +0,0 @@
# warn-off-file execute-group
execute if score timer main matches 60 run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"white","msg":""}
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 run function mq:tellraw {"text":"퀴즈를 다시 시작하시려면 종료를 눌러주세요.","color":"white","msg":""}
execute if score timer main matches 120 as @a at @s run scoreboard players set stop buttons -1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 run function mq:tellraw {"text":"플레이 해주셔서 감사합니다.","color":"white","msg":""}
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 200.. run scoreboard players set init main 11

View File

@@ -1,4 +1,3 @@
# warn-off execute-group
execute if score init main matches 0..1 run scoreboard players enable @a ready 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 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 1 run scoreboard players set @s ready 2

View File

@@ -6,6 +6,5 @@ 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 execute if score init main matches 5..6 run function mq:repeat/check_answer
# 정답 입력 다이얼로그: init=5 (곡 재생 중) 일 때만 열림 / 제출 처리 # 정답 입력 다이얼로그: init=5 (곡 재생 중) 일 때만 열림 / 제출 처리
# warn-off execute-group
execute if score init main matches 5 as @a[scores={input=1..}] run function mq:answer/open execute if score init main matches 5 as @a[scores={input=1..}] run function mq:answer/open
execute if score init main matches 5 run function mq:answer/process execute if score init main matches 5 run function mq:answer/process

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_01", "asset_id": "musicquiz:cover_01",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_01",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_02", "asset_id": "musicquiz:cover_02",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_02",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_03", "asset_id": "musicquiz:cover_03",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_03",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_04", "asset_id": "musicquiz:cover_04",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_04",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_05", "asset_id": "musicquiz:cover_05",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_05",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_06", "asset_id": "musicquiz:cover_06",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_06",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_07", "asset_id": "musicquiz:cover_07",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_07",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_08", "asset_id": "musicquiz:cover_08",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_08",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_09", "asset_id": "musicquiz:cover_09",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_09",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_10", "asset_id": "musicquiz:cover_10",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_10",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_11", "asset_id": "musicquiz:cover_11",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_11",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_12", "asset_id": "musicquiz:cover_12",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_12",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_13", "asset_id": "musicquiz:cover_13",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_13",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_14", "asset_id": "musicquiz:cover_14",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_14",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_15", "asset_id": "musicquiz:cover_15",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_15",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_16", "asset_id": "musicquiz:cover_16",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_16",
"author": "musicquiz"
} }

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:cover_17", "asset_id": "musicquiz:cover_17",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "cover_17",
"author": "musicquiz"
} }

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_18",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_19",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_20",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_21",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_22",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_23",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_24",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_25",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_26",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_27",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_28",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_29",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_30",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_31",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_32",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_33",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_34",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_35",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_36",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_37",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_38",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_39",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_40",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_41",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_42",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_43",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_44",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_45",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_46",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_47",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_48",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_49",
"width": 1,
"height": 1
}

View File

@@ -0,0 +1,5 @@
{
"asset_id": "musicquiz:cover_50",
"width": 1,
"height": 1
}

View File

@@ -1,7 +1,5 @@
{ {
"asset_id": "musicquiz:gif", "asset_id": "musicquiz:gif",
"width": 2, "width": 1,
"height": 2, "height": 1
"title": "gif",
"author": "musicquiz"
} }

View File

@@ -1,111 +0,0 @@
# 부분 적용 가이드 (→ v1.0.26)
전체 datapack zip 을 새로 풀지 않고, 이 폴더의 파일만 같은 경로에
덮어쓰면 v1.0.26 와 동일한 동작이 됩니다. 본인 월드 좌표·곡 목록 등이
들어 있는 `init/*.mcfunction` 은 일부러 포함하지 않았습니다 — 덮어쓰면
좌표가 날아가니까요.
## v1.0.26 = v1.0.25 + 문서 정리
v1.0.26 자체에는 데이터팩 동작 변경이 없습니다. 변경 사항:
- 삭제됐던 `docs/mc_video_player_mod_integration.md` 복구.
- 루트 `README.md` 의 오류 정정:
- 음원 채널 `weather` → 실제는 `player` (UI 비프만 `weather`) 로 정정.
- 폐기된 `marker` 스토리지/엔티티 설명을 “legacy 정리용” 으로 정리.
- `temp/` 부분 적용 패키지 v1.0.26 기준으로 복구 (이 폴더).
v1.0.25 의 데이터팩 코드를 이미 쓰고 있다면 mods · 데이터팩 변경 없이
끝납니다. **이 폴더를 적용해야 하는 건 v1.0.25 이전 버전에서 올라오는
경우** 입니다.
## 같이 들어 있는 누적 fix (v1.0.19 ~ v1.0.25)
### 버튼 인프라 (`repeat/buttons/`)
- `btn.mcfunction` / `btn_prep.mcfunction` / `handler.mcfunction`
- v1.0.20: `positioned $(x).0 $(y).0 $(z).0` (정수 vec3 의 자동 +0.5 보정
회피), `btn_prep``merge from` defaults 패턴 (`execute unless data
storage ...` 파서 거부 회피).
- v1.0.21: interaction 3 타일 분할 (`width × width` 정사각형 강제 회피),
`text_display` 도입, text component 직접 표기 (1.20.5+ JSON string
렌더 회피).
- v1.0.24: interaction 깊이 부호 정정 (벽 안 → 플레이어 쪽), `text_display`
Y 위치 보정, 라벨 bold.
- v1.0.25: hitbox 미세조정 (가운데 타일 폭 0.13, height 0.26, 깊이
0.07/0.93, text_display Y `~-0.5`), 셀렉터 정렬 통일.
### 모드 게이트 (`commands/start.mcfunction` + `load.mcfunction`)
- v1.0.23 / v1.0.25: `#server mq_chat_mod` 점수로 `mc_chat_answer_mod`
설치 검증, `#server mq_video_mod` + `<player> mq_video_mod` 점수로
`mc_video_player_mod` 서버/클라 설치 검증. 미설치 시 사유와 함께 차단.
- `load.mcfunction` 이 objective 생성 + `#server` 0 materialize.
### 타이머 분할 (`repeat/timer.mcfunction` + `repeat/timers/`)
- v1.0.25: 큰 `timer.mcfunction` 을 init 단계별 (`init2` 카운트다운,
`init6` 다음 곡, `init10` 엔딩) 서브함수로 분할. `timer.mcfunction`
init 게이팅만 하고 각 서브를 호출.
## 적용 방법
### 1. 모드 jar 확인 (이미 설치돼 있으면 skip)
- `mc_chat_answer_mod` v1.3.7+ : 서버 mods 폴더.
https://git.tkrmagid.kr/tkrmagid/mc_chat_answer_mod/releases/tag/v1.3.7
- `mc_video_player_mod` : 서버 mods + 클라이언트 mods 양쪽 설치 필요.
### 2. 데이터팩 파일 덮어쓰기
서버의 datapack 폴더 (예: `world/datapacks/music_quiz/`) 기준으로 이 폴더
아래 파일을 **같은 경로에 덮어쓰세요**.
```
temp/data/mq/function/commands/start.mcfunction
-> <datapack>/data/mq/function/commands/start.mcfunction
temp/data/mq/function/load.mcfunction
-> <datapack>/data/mq/function/load.mcfunction
temp/data/mq/function/repeat/buttons/btn.mcfunction
-> <datapack>/data/mq/function/repeat/buttons/btn.mcfunction
temp/data/mq/function/repeat/buttons/btn_prep.mcfunction
-> <datapack>/data/mq/function/repeat/buttons/btn_prep.mcfunction
temp/data/mq/function/repeat/buttons/handler.mcfunction
-> <datapack>/data/mq/function/repeat/buttons/handler.mcfunction
temp/data/mq/function/repeat/timer.mcfunction
-> <datapack>/data/mq/function/repeat/timer.mcfunction
temp/data/mq/function/repeat/timers/init2.mcfunction
-> <datapack>/data/mq/function/repeat/timers/init2.mcfunction
temp/data/mq/function/repeat/timers/init6.mcfunction
-> <datapack>/data/mq/function/repeat/timers/init6.mcfunction
temp/data/mq/function/repeat/timers/init10.mcfunction
-> <datapack>/data/mq/function/repeat/timers/init10.mcfunction
```
`repeat/timers/` 폴더는 없을 수도 있습니다 — 그 경우 새로 생성하고 안에
파일 3 개를 넣으세요.
### 3. (옵션) 버튼에 라벨 표시하고 싶으면 `init/buttons.mcfunction` 편집
좌표를 건들지 않기 위해 `init/buttons.mcfunction` 은 포함하지 않았습니다.
직접 편집해서 각 `button_defs` 항목에 `label` 필드를 추가하면 됩니다.
옵션 필드는 `label`, `label_color` (기본 `black`), `label_font` (기본
`minecraft:default`), `label_scale` (기본 `"1.0"`).
```
data modify storage mq:main button_defs append value {n:"start", x:..., y:..., z:..., f:"south", c:"function mq:commands/start with storage mq:main", label:"게임시작"}
```
### 4. /reload
```
/reload
```
## 확인
- 콘솔에 `btn_prep` / `btn` 파싱 에러 없음.
- 버튼 클릭 시 stone_button powered 애니메이션 발생하지 않음
(interaction 박스가 한 두께 바깥에서 ray 를 가로채므로).
- 라벨이 버튼 바로 아래 벽면에 굵게 표시.
- 채팅정답 / 영상재생 모드 미설치 시 `/start` 가 사유 메시지와 함께 차단.
- 모드 둘 다 정상 설치되어 있으면 `/start` 정상 진행.

View File

@@ -1,41 +0,0 @@
execute if score init main matches 10 run return run function mq:tellraw {"text":"퀴즈가 완전히 종료된후 시작해주세요.","color":"red","msg":""}
# ---- 외부 모드 설치 검증 ----
# 두 모드는 성격이 달라서 검증 방식이 다름:
#
# * mq_chat_mod : mc_chat_answer_mod = 서버 전용 모드 (채팅 가로채기는
# 서버에서 일어남, 클라 설치 불필요). 따라서 fake player `#server`
# 점수를 모드가 매 server tick 마다 1 로 set. 서버에 모드가 없으면
# 이 점수가 갱신되지 않음.
#
# * mq_video_mod : mc_video_player_mod = 클라이언트 측 렌더링 + 서버 측
# 컴포넌트. 같은 objective 안에 holder 두 종류 사용:
# - `#server mq_video_mod` : 서버 컴포넌트가 매 tick 1 로 갱신 (server
# presence). 없으면 0 → 서버에 모드 미설치.
# - `<player> mq_video_mod` : 클라 join 시 payload 가 서버로 오면 서버
# 컴포넌트가 해당 플레이어 점수를 1 로 set (client presence). 클라
# 미설치면 0 유지.
# 이렇게 분리해야 "서버 미설치"와 "특정 플레이어 클라 미설치"가 안내에서
# 구분된다.
#
# 1) 서버 측 모드 부재 — 전원 차단, 단일 안내. 서버 부재는 클라 검사보다
# 우선해야 — 클라가 다 설치되어 있어도 서버가 없으면 동작 안 한다.
execute unless score #server mq_chat_mod matches 1 run return run function mq:tellraw {"text":"채팅정답 모드가 서버에 미설치 — 서버 관리자에게 문의해주세요.","color":"red","msg":""}
execute unless score #server mq_video_mod matches 1 run return run function mq:tellraw {"text":"영상재생 모드가 서버에 미설치 — 서버 관리자에게 문의해주세요.","color":"red","msg":""}
# 2) 클라이언트 측 모드 (mc_video_player_mod) 부재 — 본인 누락 안내 + 차단.
# selector `scores={X=..0}` 는 점수 미존재를 매치하지 않으므로 직전에
# `add @a ... 0` 으로 materialize. 개인 안내는 tellraw @s 직접 (mq:tellraw
# 는 내부 @a broadcast 라 부적합).
scoreboard players add @a mq_video_mod 0
execute as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 모드 적용 후 다시 입장해주세요.","color":"red"}]
execute if entity @a[scores={mq_video_mod=..0}] 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

View File

@@ -1,47 +0,0 @@
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
# 외부 모드 존재 확인용 점수.
# mq_chat_mod : 서버 전용 모드(mc_chat_answer_mod). 모드가 매 server tick
# 마다 fake player `#server` 점수를 1 로 set. 모드가 서버에 없으면 0 유지.
# mq_video_mod : 클라이언트 모드(mc_video_player_mod). 클라 join 시 서버로
# handshake payload 전송 → 서버 측 모드가 해당 플레이어 점수를 1 로 set.
# 클라에 모드가 없으면 0 유지. (login.mcfunction 에서 플레이어별 0 초기화.)
scoreboard objectives remove mq_chat_mod
scoreboard objectives remove mq_video_mod
scoreboard objectives add mq_chat_mod dummy
scoreboard objectives add mq_video_mod dummy
# /reload 후 모드가 한 tick 도 돌기 전에 start 가 호출될 수 있으니
# #server 점수도 0 으로 materialize. 모드가 살아 있으면 다음 tick 에 1 로 갱신.
# mq_video_mod 도 같은 objective 안에서 holder 만 다르게 — `#server` 는 서버
# 컴포넌트 존재 (서버 측 모드가 매 tick 1 로 갱신), `<player>` 는 클라 측
# 존재 (payload 수신 시 1 로 갱신).
scoreboard players set #server mq_chat_mod 0
scoreboard players set #server mq_video_mod 0
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

View File

@@ -1,117 +0,0 @@
# warn-off-file always-pass-condition
# 버튼 1개에 대한 매 tick 처리.
# 매크로 인자(mq:tmp.btn): n, x, y, z, f, c, label, label_color, label_font, label_scale
# buttons 점수 상태:
# ..-2 : 비활성 (버튼 블록 제거, interaction 응답 차단)
# -1 : 초기화 단계 (버튼 블록 + interaction × 3 + text_display 보장 후 0)
# 0 : 정상 (interaction 클릭 대기)
#
# interaction/text_display entity 는 데이터팩이 직접 summon — /reload 시
# commands/stop 에서 buttons 가 -1 로 재설정되어 다음 tick 에 ensure 로직
# 실행. -1 단계에서 같은 태그 entity 를 모두 kill 후 정확한 개수만 다시
# summon → 항상 idempotent (dup 누적 없음, 좌표/라벨 갱신 자동 반영).
#
# ---- facing → 머리 hitbox 위치 (이 파일 한 곳에서만 정의) ----
# stone_button[face=wall, facing=X] AABB (블록 상대 좌표):
# facing 의 의미 = "버튼 머리 visible 면의 normal 방향". 머리는 그 방향
# 쪽 face 에 붙어 있고 hitbox 는 그 face 에서 안쪽(1/8) 만큼 들어감.
# south : z ∈ [0, 0.125] 가로 x ∈ [0.3125, 0.6875]
# north : z ∈ [0.875, 1] 가로 x ∈ [0.3125, 0.6875]
# east : x ∈ [0, 0.125] 가로 z ∈ [0.3125, 0.6875]
# west : x ∈ [0.875, 1] 가로 z ∈ [0.3125, 0.6875]
# 세로 y ∈ [0.375, 0.625] 공통.
#
# interaction entity 의 horizontal hitbox 는 width × width 정사각형 강제라
# 단일 entity 로는 "가로 0.375 × 두께 0.125" 직사각형 불가. → 두께(0.125)
# 와 같은 width=0.125 인 interaction 을 가로축으로 3 개 타일링 (gap 없이
# 인접: 중심 0.375 / 0.5 / 0.625, 각 폭 0.125 → 합 [0.3125, 0.6875]).
# interaction Y 는 hitbox 바닥 → 소환 y = block y + 0.375, height = 0.25.
#
# ---- 깊이축: 블록 면 바로 바깥, 플레이어 쪽 (이중 트리거 방지) ----
# interaction 박스가 stone_button hitbox 와 겹치면 한 번 클릭에 interaction
# 도 발화하고 stone_button 도 vanilla 클릭으로 인식되어 powered=true 애니
# 메이션이 같이 일어남. → interaction 박스를 버튼 머리 바깥쪽 (플레이어
# 측) 으로 한 두께 (0.125) 만큼 밀어 ray 가 stone_button 에 닿기 전에
# interaction 에서 멈추게.
#
# 주의: facing 은 "버튼 머리 normal 방향" = 플레이어가 보는 방향.
# south 면 머리 +z 향함, 벽은 -z 쪽. 따라서 플레이어 쪽 = +z = interaction
# 을 z > 버튼 머리 (0.125) 영역으로. (v1.0.21 에서 한 두께만큼 뺀다는
# 의도였는데 부호를 반대로 잡아 interaction 이 벽 안으로 들어가 있었음.)
# south : 깊이 z 중심 = 0.1875 (interaction z ∈ [0.125, 0.25], 버튼 z ∈ [0, 0.125])
# north : 깊이 z 중심 = 0.8125 (interaction z ∈ [0.75, 0.875], 버튼 z ∈ [0.875, 1])
# east : 깊이 x 중심 = 0.1875
# west : 깊이 x 중심 = 0.8125
#
# ---- positioned 의 .5 보정 회피 ----
# MC 의 vec3 인자는 정수만 쓰면 자동으로 +0.5 보정됨 (블록 중심으로 잡힘).
# positioned 2773 86 5968 → 실제로는 (2773.5, 86, 5968.5). 거기서 ~ 오프셋
# 을 더하면 박스 전체가 0.5 칸 어긋남. $(x).0 $(y).0 $(z).0 처럼 decimal
# 형태로 넘기면 보정 없이 정확한 블록 origin (minimal corner) 이 됨.
#
# ---- text_display 위치 (버튼 바로 아래 같은 벽면에 가운데 정렬) ----
# 버튼 아래 블록의 같은 벽면 (visible 면, 플레이어 쪽) 에 살짝 띄워 부착.
# 가로축: ~0.5 (block 가로 중심, alignment=center 기본값과 합쳐져서 라벨
# 자체도 수평 중앙).
# 세로축: text_display 의 entity Y 는 텍스트 윗변 — 아래로 자람. ~-0.25
# 로 두면 텍스트 윗변이 Y-0.25 (버튼 바로 아래), 한 줄(기본 ~0.5 블록 높이)
# 이 Y-0.75 까지 내려와 버튼 아래 한 칸 벽면 [Y-1, Y] 의 위쪽 절반에
# 자리잡음 — 시각적으로 버튼 바로 밑 가운데 라벨.
# south : ~0.5 ~-0.25 ~0.01 yaw 0 (head 가 +z → 벽면 z=0 에서 +0.01 띄움)
# north : ~0.5 ~-0.25 ~0.99 yaw 180
# east : ~0.01 ~-0.25 ~0.5 yaw -90
# west : ~0.99 ~-0.25 ~0.5 yaw 90
# ---- 비활성: 블록 + interaction × 3 + text_display 전부 제거 후 종료 ----
# data modify entity @e[...] 는 대상 1개 강제 → interaction 3개 모드에선
# 못 쓰므로 그냥 kill. 어차피 버튼 블록도 air 로 바꾸므로 라벨도 같이 제거.
$execute if score $(n) buttons matches ..-2 run setblock $(x) $(y) $(z) minecraft:air
$execute if score $(n) buttons matches ..-2 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction]
# $execute if score $(n) buttons matches ..-2 run kill @e[type=minecraft:text_display,tag=mq,tag=$(n)]
$execute if score $(n) buttons matches ..-2 run return 0
# ---- 초기화: 블록 + interaction × 3 + text_display 보장 ----
$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 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction]
$execute if score $(n) buttons matches -1 run kill @e[distance=0..,tag=mq,tag=$(n),type=minecraft:text_display]
# south: 깊이축=z(+0.1875, 플레이어 쪽), 가로축=x, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.37 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.5 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.63 ~0.37 ~0.07 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"south"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.5 ~-0.5 ~0.01 {Tags:["mq","$(n)"],Rotation:[0f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# north: 깊이축=z(+0.8125, 플레이어 쪽), 가로축=x, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.37 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.5 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.63 ~0.37 ~0.93 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"north"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.5 ~-0.5 ~0.99 {Tags:["mq","$(n)"],Rotation:[180f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# east: 깊이축=x(+0.1875, 플레이어 쪽), 가로축=z, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.37 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.5 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.07 ~0.37 ~0.63 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"east"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.01 ~-0.5 ~0.5 {Tags:["mq","$(n)"],Rotation:[-90f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
# west: 깊이축=x(+0.8125, 플레이어 쪽), 가로축=z, 3 타일 + 라벨
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.37 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.5 {Tags:["mq","$(n)"],width:0.13f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:interaction ~0.93 ~0.37 ~0.63 {Tags:["mq","$(n)"],width:0.125f,height:0.26f,response:0b}
$execute if score $(n) buttons matches -1 unless data storage mq:tmp btn{label:""} if data storage mq:tmp btn{f:"west"} positioned $(x).0 $(y).0 $(z).0 run summon minecraft:text_display ~0.99 ~-0.5 ~0.5 {Tags:["mq","$(n)"],Rotation:[90f,0f],background:0,text:{text:"$(label)",color:"$(label_color)",font:"$(label_font)",bold:true},transformation:{scale:[$(label_scale)f,$(label_scale)f,$(label_scale)f],translation:[0f,0f,0f],left_rotation:[0f,0f,0f,1f],right_rotation:[0f,0f,0f,1f]}}
$execute if score $(n) buttons matches -1 run scoreboard players set $(n) buttons 0
# ---- 상시: interaction 클릭/타격 → playsound + 명령/투표 실행 ----
# init main = 0 (퀴즈 시작 전 설정 단계) : 명령 직접 실행
# 그 외 : trigger 투표 경로
# 한 버튼에 interaction 3개지만 `on target` 은 클릭된 1개만 통과
# (나머지는 target 부재로 체인 중단). limit=1 을 두면 MC 가 임의로 1개를
# 골라 잘못된 entity 만 검사하므로 limit 두지 않음.
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 run playsound minecraft:block.stone_button.click_on block @s ~ ~ ~ 1 1
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 if score init main matches 0 run $(c)
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target as @s positioned $(x).0 $(y).0 $(z).0 unless score init main matches 0 run trigger $(n)
# ---- 처리 후 attack/interaction NBT 클리어 (다음 tick 중복 발화 방지) ----
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] at @s run data remove entity @s attack
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] at @s run data remove entity @s interaction

View File

@@ -1,21 +0,0 @@
# 한 button entry 의 optional 필드 기본값을 채워 macro 호출 시 $(arg) 미존재
# 에러를 방지한다. handler 에서 entry 복사 직후 호출.
#
# label : 없으면 "" (빈 문자열) -> btn 안의 text_display 분기는 label
# 이 "" 이면 스킵.
# label_color : 기본 "black"
# label_font : 기본 "minecraft:default"
# label_scale : 기본 "1.0" (Vector3f 의 한 축, 3축 동일하게 사용됨)
#
# 구현: defaults 컴파운드를 먼저 만들고 entry (mq:tmp.btn) 를 그 위에 merge
# 한 뒤 다시 mq:tmp.btn 으로 되돌린다. data modify ... merge from 은 source
# compound 의 키로 target compound 를 덮어쓰므로 entry 에 있는 값은 보존되고
# entry 에 없는 키만 default 값으로 채워진다.
#
# (이전에 `execute unless data storage mq:tmp btn.label run data modify ...`
# 방식이었으나 MC 26.1.2 parser 가 해당 라인을 거부했음. merge 방식은 문제
# 난 execute-unless-data 구문 자체를 제거.)
data modify storage mq:tmp btn_default set value {label:"",label_color:"black",label_font:"minecraft:default",label_scale:"1.0"}
data modify storage mq:tmp btn_default merge from storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:tmp btn_default

View File

@@ -1,27 +0,0 @@
# 각 button_defs 항목을 mq:tmp.btn 으로 복사 → optional 필드 기본값 채움
# → btn 호출. btn 안에서 facing 별 분기 (if data storage mq:tmp btn{f:"..."})
# 와 macro arg ($(label) 등) 둘 다 사용 가능하게 같은 storage 에 노출시킨다.
data modify storage mq:tmp btn set from storage mq:main button_defs[0]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:main button_defs[1]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:main button_defs[2]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:main button_defs[3]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:main button_defs[4]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:main button_defs[5]
function mq:repeat/buttons/btn_prep
function mq:repeat/buttons/btn with storage mq:tmp btn

View File

@@ -1,15 +0,0 @@
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 run function mq:repeat/timers/init2
# next song timer
execute if score init main matches 6 run function mq:repeat/timers/init6
# endding timer
execute if score init main matches 10 run function mq:repeat/timers/init10

View File

@@ -1,3 +0,0 @@
execute if score timer main matches 300 run title @a title {"text":""}
execute if score timer main matches 290 run function mq:images/clear
execute if score timer main matches 300.. run function mq:quiz/select with storage mq:main

View File

@@ -1,15 +0,0 @@
# warn-off-file execute-group
execute if score timer main matches 20 run title @a title {"text":"3"}
execute 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 timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 40 run title @a title {"text":"2"}
execute 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 timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 60 run title @a title {"text":"1"}
execute 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 timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone weather @s ~ ~ ~ 1 1
execute 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 timer main matches 100 run title @a title {"text":""}
execute if score timer main matches 100.. run function mq:quiz/select with storage mq:main

View File

@@ -1,15 +0,0 @@
# warn-off-file execute-group
execute if score timer main matches 60 run function mq:tellraw {"text":"퀴즈가 종료되었습니다.","color":"white","msg":""}
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 run function mq:tellraw {"text":"퀴즈를 다시 시작하시려면 종료를 눌러주세요.","color":"white","msg":""}
execute if score timer main matches 120 as @a at @s run scoreboard players set stop buttons -1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 120 run function mq:tellraw {"text":"플레이 해주셔서 감사합니다.","color":"white","msg":""}
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click weather @s ~ ~ ~ 1 1
execute if score timer main matches 200.. run scoreboard players set init main 11