14 Commits

Author SHA1 Message Date
Claude (owner)
da075b60b8 music_quiz: 버튼 동시 눌림 방지 + text_display 텍스트 컴포넌트 형식 수정
(a) interaction 박스가 stone_button hitbox 와 겹쳐 있어서 한 번 클릭에
    interaction 도 발화하고 stone_button 도 vanilla 클릭으로 인식되어
    powered=true 애니메이션이 같이 일어나던 문제. interaction 박스를 버튼
    면 바깥쪽으로 한 두께(0.125) 만큼 빼서 ray 가 stone_button 에 닿기
    전에 interaction 에서 멈추도록.
    south z: 0.0625 → -0.0625, north z: 0.9375 → 1.0625
    east  x: 0.0625 → -0.0625, west  x: 0.9375 → 1.0625

(b) MC 1.20.5+ 부터 text_display.text 는 String 이 아닌 직접 TextComponent
    compound. 이전에 String 안에 JSON 을 넣어서 그 JSON 자체가 텍스트로
    렌더링되던 문제 (`{"text":"게임시작",...}` 가 그대로 보임). compound
    형태 `text:{text:"...",color:"...",font:"..."}` 로 변경.

temp/ 부분 적용 패키지의 btn.mcfunction 과 README 도 동기화.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 23:50:04 +09:00
Claude (owner)
b803a422a3 music_quiz: 버튼 interaction 0.5칸 어긋남 + btn_prep 파싱 에러 근본 수정
- btn.mcfunction 의 positioned $(x) $(y) $(z) → $(x).0 $(y).0 $(z).0.
  마인크래프트 vec3 인자는 정수만 쓰면 자동으로 +0.5 보정 (블록 중심으로
  잡힘) 되기 때문에 positioned 2773 86 5968 이 실제로는 (2773.5, 86,
  5968.5) 가 되고, 거기서 ~0.375 ~0.0625 같은 오프셋을 더해 interaction
  을 소환하면 박스가 블록 중앙으로 0.5 칸 밀린다. decimal 형태로 강제해
  보정 회피.

- btn_prep.mcfunction 을 execute-unless-data 방식에서 defaults 컴파운드 +
  merge 방식으로 재작성. 이전 v1.0.19 의 다중 공백 정규화는 근본 원인이
  아니었음 — `execute unless data storage mq:tmp btn.label run ...` 구문
  자체를 MC 26.1.2 파서가 거부. data modify ... merge from 으로 source 의
  키가 target 을 덮어쓰는 머지를 활용하면 같은 기능을 문제 라인 없이 구현.

- temp/ 부분 적용 패키지의 README 와 두 파일을 동기화. 진짜 원인 설명으로
  재작성.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 21:40:32 +09:00
Claude (owner)
5d610cf01a temp: v1.0.18→v1.0.19 부분 적용용 수정 파일과 README 추가
전체 datapack 교체 없이 두 파일만 덮어써서 v1.0.19 와 동일 상태로
만들 수 있도록 temp/ 에 수정된 파일과 적용 가이드만 남긴다.

- temp/data/mq/function/repeat/buttons/btn_prep.mcfunction
- temp/data/mq/function/repeat/buttons/btn.mcfunction
- temp/README.md (부분 적용 방법)

이전에 있던 temp/gif.png, gif.png.mcmeta 는 이번 부분 적용 패키지와
무관해서 제거.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 21:30:08 +09:00
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
110 changed files with 432 additions and 1140 deletions

236
README.md
View File

@@ -16,91 +16,26 @@
### 호환 버전 ### 호환 버전
- **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% 바닐라 — 의존 플러그인 없음
모드는 **필수 1 + 선택 1** 구성이다.
- **`mc_chat_answer_mod` v1.3.7+ (필수)** — 서버 전용. 채팅으로 정답 입력을
받는다. 정답 판정의 핵심이라 없으면 `/start` 가 차단된다. 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` (선택)** — 정답 영상 재생용. **없어도 게임은
정상 진행되며 영상만 표시되지 않는다.** `/start` 는 차단하지 않고 노란
경고만 출력한다 (서버 부재 시 전체 안내, 특정 플레이어 클라 부재 시 본인
에게만 안내). presence 는 `#server mq_video_mod` (서버 컴포넌트가 매 tick
1 로 갱신) + `<player> mq_video_mod` (클라 join handshake 수신 시 1 로 set)
로 나눠 표시한다. 모드 측 연동 사양(presence handshake)은
[docs/mc_video_player_mod_integration.md](docs/mc_video_player_mod_integration.md) 참고.
https://git.tkrmagid.kr/tkrmagid/mc_video_player_mod
> 데이터팩은 모드 명령(`videoCache`/`videoPlace`/`videoDelete`)을 mcfunction
> 안에서 직접 실행하지 않고 **NBT 문자열 큐 → 커맨드블럭** 으로만 내보낸다.
> 덕분에 영상 모드가 없어도 데이터팩 로드 시 미등록 명령 파싱 에러가 나지
> 않는다 (자세한 건 아래 "정답 영상" 절).
### 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]` 로 제거.
- gif 페인팅: `images/gif.mcfunction` 을 실행하면 실행한 플레이어에게
`mq:gif` variant 페인팅 아이템을 지급한다. 벽에 설치하면 랜덤 선택 없이
`mq:gif` 로 고정 배치되고, 리소스팩의 애니메이션 텍스처
(`musicquiz:gif` `.png` + `.png.mcmeta`) 덕분에 gif 처럼 움직인다.
variant 정의는 `painting_variant/gif.json` (`asset_id: musicquiz:gif`).
리소스팩 트랙 번호와 `mq:init/songs` 리스트의 순서가 1:1로 일치해야 한다. 리소스팩 트랙 번호와 `mq:init/songs` 리스트의 순서가 1:1로 일치해야 한다.
1번째 곡 → `track_01` / `cover_01`, … (2자리 zero-pad). 1번째 곡 → `track_01` / `cover_01`, … (2자리 zero-pad).
### 정답 영상 (선택)
`mc_video_player_mod` 가 설치돼 있으면 정답 공개 시 곡별 영상을 벽면에
띄운다. 모드가 없어도 게임은 정상 진행되며 영상만 표시되지 않는다.
핵심은 **모드 명령을 mcfunction 안에서 직접 실행하지 않는다**는 점이다.
모드 명령(`videoCache add/remove/clear`, `videoPlace`, `videoDelete`)은
미설치 시 데이터팩 로드 단계에서 미등록 명령 파싱 에러를 일으키므로,
NBT 문자열로만 만들어 큐에 적재하고 **커맨드블럭**으로 실행한다.
- **큐 적재** — `mq:videos/macro/cmd` 가 명령을 문자열로 조립해
`mq:main video.cmdq` 리스트(`{c:"<명령 문자열>"}`)에 push 한다.
문자열은 mcfunction 파서를 거치지 않으므로 모드가 없어도 로드된다.
- **드레인** — `mq:videos/drain` (매 tick, `mq:tick` 에서 호출) 이 큐를
한 tick 에 한 개씩 꺼내 `cmd_x/cmd_y/cmd_z` 좌표에 커맨드블럭을
setblock 하고 `auto` 를 켜 1회 실행한다. 커맨드블럭은 `auto:1b` 면 매
tick 재실행되므로, `video.armed` 플래그로 실행한 다음 tick 에
`mq:videos/macro/drain_off``auto:0b` 로 꺼 **정확히 1회만** 실행되게
한다 (모드의 `videoCache add` 가 중복 이름을 실패 처리해 스팸나는 것 방지).
- **캐시(preload / FIFO keep)** — 게임 시작 시 `videos/cache/preload`
1..`preload` 번 영상을 미리 받아두고, 라운드마다 `videos/cache/add`
N개 앞까지 캐시를 채운다. 캐시 개수가 `keep` 을 넘으면
`videos/cache/evict` 가 가장 먼저 받은 것부터 FIFO 로 축출한다
(`keep<=0` 이면 무제한). 데이터팩이 `mq:main video.cached`
(`{i:N}` 리스트) 로 캐시 상태를 1:1 추적해 중복 등록을 막고 src 를 고른다.
- **동적 src** — 재생 시 해당 번호가 캐시에 있으면 등록 이름 `video_N` 으로,
없으면 전체 URL `$(namespace)/N` 으로 `videoPlace` 한다.
`videos/normalize``namespace` 끝의 `/` 를 떼어 경로 중복 `//` 를 막는다.
모드 측이 구현해야 하는 presence handshake(서버 tick + 클라 payload) 사양은
[docs/mc_video_player_mod_integration.md](docs/mc_video_player_mod_integration.md)
참고.
### 게임 흐름과 상태 (`init` 스코어보드) ### 게임 흐름과 상태 (`init` 스코어보드)
게임 전체 상태는 `main` 스코어보드의 가상 플레이어 `init` 값으로 관리한다. 게임 전체 상태는 `main` 스코어보드의 가상 플레이어 `init` 값으로 관리한다.
@@ -136,44 +71,29 @@ NBT 문자열로만 만들어 큐에 적재하고 **커맨드블럭**으로 실
### 입력 버튼 ### 입력 버튼
관리자가 사용하는 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
@@ -186,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/ # 게임 진행 로직
@@ -197,31 +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 일괄 제거
│ │ ├── gif.mcfunction # 실행 플레이어에게 mq:gif 페인팅 아이템 지급
│ │ └── macro/show.mcfunction # $summon painting 매크로 │ │ └── macro/show.mcfunction # $summon painting 매크로
│ ├── videos/ # 정답 영상 (선택 모드, 커맨드블럭 큐 경로)
│ │ ├── show.mcfunction # "play" 큐 적재
│ │ ├── clear.mcfunction # "delete" 큐 적재
│ │ ├── normalize.mcfunction # namespace 끝 / 제거 + 캐시 상태 초기화
│ │ ├── drain.mcfunction # 매 tick 큐 1개씩 커맨드블럭 실행 (mq:tick)
│ │ ├── cache/{fill,add,preload,evict,clear}.mcfunction # preload/FIFO 캐시 관리
│ │ └── macro/ # 매크로 진입점
│ │ ├── cmd.mcfunction # 모드 명령 문자열 조립 → cmdq push
│ │ ├── drain.mcfunction # $setblock command_block + auto 1b
│ │ └── drain_off.mcfunction # $auto 0b (다음 tick 재실행 방지)
│ ├── 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
``` ```
@@ -235,10 +142,6 @@ music_quiz/
- `func.temp` — 산술용 임시 상수 (`two=2` 등) - `func.temp` — 산술용 임시 상수 (`two=2` 등)
- `leave_game``custom:leave_game` 통계 (재접속 감지) - `leave_game``custom:leave_game` 통계 (재접속 감지)
- `score` — 사이드바 표시용 점수 - `score` — 사이드바 표시용 점수
- `mq_chat_mod``mc_chat_answer_mod` presence. `#server` 점수 1 이면 설치됨
(없으면 `/start` 차단)
- `mq_video_mod``mc_video_player_mod` presence. `#server` = 서버 측 모드
존재, `<player>` = 클라 측 모드 존재 (없으면 경고만, 차단 X)
- `ready` / `cancel` / `stop` / `skip` / `hint` / `replay` — 플레이어 트리거 - `ready` / `cancel` / `stop` / `skip` / `hint` / `replay` — 플레이어 트리거
스토리지: 스토리지:
@@ -247,33 +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}` — 정답 페인팅 좌표
- `video` = `{namespace, cmd_x/y/z, x/y/z, facing, w, h, sound, preload, keep}` - `marker` = `{x, y, z}` — 정답 입력 marker 엔티티 위치
— 정답 영상 설정 + 런타임 캐시 상태. `cmd_*` 는 큐 실행용 커맨드블럭
좌표, `x/y/z`·`facing`·`w/h` 는 영상 표시 위치/크기. 런타임에
`cached`(`{i:N}` 리스트), `cmdq`(명령 큐), `armed`(커맨드블럭 1회 실행
플래그) 가 이 컴파운드에 추가된다
- `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`), 정답 영상 설정(`video`). `title``[ … ]` 정답 페인팅 좌표(`image`), marker 엔티티 좌표. `title``[ … ]`
채팅 접두사로도 사용된다. `video``namespace` 는 영상 URL 베이스, 채팅 접두사로도 사용된다.
`cmd_x/y/z` 는 모드 명령 실행용 커맨드블럭 좌표, `x/y/z`·`facing`·`w/h`
는 영상 표시 위치/크기, `sound` 는 음량(-1 음소거), `preload` 는 미리
받아둘 캐시 개수(0 이면 끔), `keep` 은 FIFO 유지 개수(0 이하 무제한).
- **`init/songs.mcfunction`** — 곡 목록 (한 줄에 한 곡씩 append). `alias` - **`init/songs.mcfunction`** — 곡 목록 (한 줄에 한 곡씩 append). `alias`
배열의 문자열은 정답 판정 시 동의어로 인정된다. **곡의 순서가 리소스팩 배열의 문자열은 정답 판정 시 동의어로 인정된다. **곡의 순서가 리소스팩
트랙 번호와 1:1 매칭** 되므로 순서 변경 시 리소스팩도 함께 재생성해야 트랙 번호와 1:1 매칭** 되므로 순서 변경 시 리소스팩도 함께 재생성해야
@@ -292,84 +183,31 @@ JSON 텍스트 컴포넌트가 storage 참조를 일관되게 지원하지 않
### 설치 ### 설치
1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사. 1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사.
2. 서버 mods 폴더에 `mc_chat_answer_mod` v1.3.7+ (필수) 설치. 2. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용
`mc_video_player_mod` (선택) 는 정답 영상을 쓸 때만 서버 + 클라이언트
양쪽에 설치한다. 없어도 게임은 정상 동작하며 영상만 표시되지 않는다.
3. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용
(런처가 자동 처리). (런처가 자동 처리).
4. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상. 3. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상.
5. `mq:init/buttons` 에 정의된 좌표 부근에 6개 버튼이 자동 배치된다. 4. 좌표 `144, 62, -225` 부근에 6개 버튼이 자동 배치된다.
6. `start` 버튼을 눌러 게임 시작`mc_chat_answer_mod` (필수) 미설치 시 5. `start` 버튼을 눌러 게임 시작.
사유와 함께 차단된다. `mc_video_player_mod` (선택) 미설치 시에는 노란
경고만 출력하고 영상 없이 진행된다.
### 좌표 의존성 (주의) ### 좌표 의존성 (주의)
다음 좌표 데이터팩 안에 박혀 있어 다른 월드에서 쓰려면 직접 바꿔야 다음 좌표 데이터팩 안에 박혀 있어, 다른 월드에서 그대로 사용하려면
한다. 현재 박혀 있는 좌표는 본인 월드 기준이므로 그대로 옮겨가면 동작 `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-06-05 — 정답 영상 시스템 + 영상 모드 선택화 + gif 페인팅
- **정답 영상 추가** — `mc_video_player_mod` 연동으로 정답 공개 시 곡별
영상을 벽면에 표시. `videos/` 트리 신설 (show/clear/normalize/drain +
cache/{fill,add,preload,evict,clear} + macro/{cmd,drain,drain_off}).
- **모드 명령 커맨드블럭 큐 경로** — 모드 명령(`videoCache`/`videoPlace`/
`videoDelete`)을 mcfunction 에서 직접 실행하지 않고 NBT 문자열로 큐
(`mq:main video.cmdq`) 에 적재 → `videos/drain` 이 매 tick 한 개씩
커맨드블럭으로 실행. 미설치 모드여도 데이터팩 로드 시 파싱 에러 없음.
`video.armed` 플래그 + `drain_off` (auto:0b) 로 정확히 1회 실행 보장.
- **영상 모드 선택화** — `mc_video_player_mod` 없어도 `/start` 차단 안 함
(노란 경고만, 영상 없이 진행). 채팅 모드(`mc_chat_answer_mod`) 만 필수.
- **캐시 (preload / FIFO keep)** — `config.video``preload`/`keep` 으로
미리받기 개수와 FIFO 유지 개수 제어. `video.cached` 로 캐시 상태 자체
추적, 동적 src (`video_N` vs 전체 URL), `normalize` 가 namespace 끝 `/`
제거.
- **gif 페인팅** — `images/gif.mcfunction`: 실행 플레이어에게 `mq:gif`
variant 페인팅 아이템 지급. 벽 설치 시 리소스팩 애니메이션 텍스처로
움직임. `painting_variant/gif.json` (`asset_id: musicquiz:gif`) 신설.
- `init/config.mcfunction``video` 컴파운드 추가, `load.mcfunction`
`mq_chat_mod`/`mq_video_mod` objective 추가.
### 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

@@ -10,16 +10,12 @@
| `<player> mq_video_mod` | 해당 플레이어 클라에 모드 존재 | 서버 컴포넌트가 client→server payload 수신 시 1 | | `<player> mq_video_mod` | 해당 플레이어 클라에 모드 존재 | 서버 컴포넌트가 client→server payload 수신 시 1 |
둘 다 같은 objective `mq_video_mod` (dummy) 를 쓰고 holder 이름으로 의미를 둘 다 같은 objective `mq_video_mod` (dummy) 를 쓰고 holder 이름으로 의미를
구분한다. **영상재생 모드는 선택 모드라 미설치여도 `/start` 를 차단하지 구분한다. 데이터팩 `mq:commands/start` 가드는 두 단계로 검사:
않는다.** `mq:commands/start` 는 두 점수를 보고 **경고만** 출력한다: 서버 부재 → 전원 안내 후 차단 / 일부 플레이어 클라 부재 → 본인 안내 + 차단.
서버 부재 → 전원에게 "영상 없이 진행됩니다" / 일부 플레이어 클라 부재 →
본인에게만 "본인 화면에는 영상이 표시되지 않습니다". 게임은 그대로 진행된다.
> 참고: 자매 모드 `mc_chat_answer_mod` 는 채팅 가로채기가 본질적으로 서버 > 참고: 자매 모드 `mc_chat_answer_mod` 는 채팅 가로채기가 본질적으로 서버
> 측 동작이라 `#server mq_chat_mod` 한 가지만 쓴다 (per-player handshake 무의미). > 측 동작이라 `#server mq_chat_mod` 한 가지만 쓴다 (per-player handshake 무의미).
> 그쪽은 정답 판정의 핵심이라 **없으면 `/start` 가 실제로 차단된다.** 본 > 본 모드는 클라이언트가 직접 영상 렌더링하므로 per-player handshake 도 필요함.
> 모드는 클라이언트가 직접 영상 렌더링하므로 per-player handshake 가 필요하고,
> 그 점수는 차단이 아니라 **플레이어별 경고**에만 쓰인다.
## 데이터팩 측이 이미 제공하는 것 ## 데이터팩 측이 이미 제공하는 것
@@ -36,24 +32,25 @@ scoreboard objectives add mq_video_mod dummy
scoreboard players set @s mq_video_mod 0 scoreboard players set @s mq_video_mod 0
``` ```
`mq:commands/start.mcfunction` 경고 (차단 아님): `mq:commands/start.mcfunction` 가드:
```mcfunction ```mcfunction
# 서버 부재 우선 차단
execute unless score #server mq_video_mod matches 1 run return run function mq:tellraw {"text":"영상재생 모드가 서버에 미설치 — 서버 관리자에게 문의해주세요.","color":"red","msg":""}
# unset 매치 안 되므로 materialize # unset 매치 안 되므로 materialize
scoreboard players add @a mq_video_mod 0 scoreboard players add @a mq_video_mod 0
# 서버 부재 — 전원에게 경고만, 그대로 진행 # 본인 안내 (tellraw @s 직접 — mq:tellraw 는 @a broadcast 라 부적합)
execute unless score #server mq_video_mod matches 1 run function mq:tellraw {"text":"영상재생 모드가 서버에 미설치 — 영상 없이 진행됩니다.","color":"yellow","msg":""} execute as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 모드 적용 후 다시 입장해주세요.","color":"red"}]
# 서버는 있는데 본인 클라만 부재 — 본인에게만 경고 # 한 명이라도 누락이면 시작 차단
execute if score #server mq_video_mod matches 1 as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 본인 화면에는 영상이 표시되지 않습니다.","color":"yellow"}] execute if entity @a[scores={mq_video_mod=..0}] run return run function mq:tellraw {"text":"필수 모드 미설치 플레이어가 있어 시작할 수 없습니다.","color":"red","msg":""}
``` ```
`load.mcfunction` 에서 `#server mq_video_mod = 0` 으로 미리 깔아둠 → 서버 `load.mcfunction` 에서 `#server mq_video_mod = 0` 으로 미리 깔아둠 → 서버
컴포넌트가 한 tick 도 안 돌면 0 유지 → 서버 부재 경고 출력. 컴포넌트가 한 tick 도 안 돌면 0 유지 → 가드 차단.
즉, **서버 컴포넌트가 (a) 매 tick `#server mq_video_mod=1` 갱신, 즉, **서버 컴포넌트가 (a) 매 tick `#server mq_video_mod=1` 갱신,
(b) client payload 수신 시 송신 플레이어 점수=1 갱신**, 이 두 가지만 하면 (b) client payload 수신 시 송신 플레이어 점수=1 갱신**, 이 두 가지만 하면
나머지는 데이터팩이 알아서 한다. (점수가 0 이어도 게임은 진행되며, 경고 나머지는 데이터팩이 알아서 한다.
출력과 영상 표시 여부에만 영향을 준다.)
## 모드가 구현해야 하는 동작 ## 모드가 구현해야 하는 동작
@@ -138,13 +135,12 @@ ServerPlayNetworking.registerGlobalReceiver(HelloPayload.TYPE, (payload, context
- 데이터팩 `mq:players/login` 이 spawn dialog 통과 시점에 점수를 0 으로 - 데이터팩 `mq:players/login` 이 spawn dialog 통과 시점에 점수를 0 으로
리셋하므로, **클라 payload 주기 재전송은 필수**. login 이후 다음 재전송 리셋하므로, **클라 payload 주기 재전송은 필수**. login 이후 다음 재전송
까지의 짧은 공백에 호스트가 start 를 누르면 (차단은 아니지만) 설치된 까지의 짧은 공백에 호스트가 start 를 누르면 가드가 차단되니, 재전송
플레이어에게도 잘못된 미설치 경고가 뜰 수 있으니, 재전송 간격은 5 초 간격은 5 초 이하 권장.
이하 권장.
- 서버가 set 한 점수는 다음 join 시 login 에서 다시 0 으로 리셋됨 → 모드를 - 서버가 set 한 점수는 다음 join 시 login 에서 다시 0 으로 리셋됨 → 모드를
빼고 재접속한 플레이어가 stale 1 을 가질 일 없음. 빼고 재접속한 플레이어가 stale 1 을 가질 일 없음.
- payload 송신 → 서버 처리는 ms 단위 → 주기 재전송이 살아 있는 한 경고 - payload 송신 → 서버 처리는 ms 단위 → 주기 재전송이 살아 있는 한 start
오탐 race 없음. 타이밍 race 없음.
### 대안: `/trigger` 방식 (별도 서버 컴포넌트 불필요) ### 대안: `/trigger` 방식 (별도 서버 컴포넌트 불필요)
@@ -166,21 +162,18 @@ ServerPlayNetworking.registerGlobalReceiver(HelloPayload.TYPE, (payload, context
4. 플레이어 spawn dialog 통과 → `mq:players/login` 이 점수를 0 으로 리셋. 4. 플레이어 spawn dialog 통과 → `mq:players/login` 이 점수를 0 으로 리셋.
5. **클라 모드가 주기 재전송 (5 초 이하 권장, 필수)** → 늦어도 다음 주기에 5. **클라 모드가 주기 재전송 (5 초 이하 권장, 필수)** → 늦어도 다음 주기에
서버 모드가 다시 `mq_video_mod = 1` 로 갱신. 서버 모드가 다시 `mq_video_mod = 1` 로 갱신.
6. 호스트 start → `@a[scores={mq_video_mod=..0}]` 검사 → 클라 모드 미설치 6. 호스트 start → 가드가 `@a[scores={mq_video_mod=..0}]` 검사 → 클라 모드
플레이어 본인에게만 경고. 차단 없이 게임 진행 (해당 플레이어 화면에만 미설치 플레이어 있으면 시작 차단.
영상 미표시).
## 테스트 ## 테스트
1. **서버 미설치**: 서버에 모드 jar 가 없는 상태에서 데이터팩만 적용 → 1. **서버 미설치**: 서버에 모드 jar 가 없는 상태에서 데이터팩만 적용 →
호스트 start → "영상재생 모드가 서버에 미설치 — 영상 없이 진행됩니다." 호스트 start → "영상재생 모드가 서버에 미설치 — 서버 관리자에게..." 한
노란 경고 출력 후 **그대로 게임 진행** (영상만 안 나옴). 출력 후 차단. (`#server mq_video_mod` 가 갱신되지 않음.)
(`#server mq_video_mod` 가 갱신되지 않음.)
2. **서버 설치 + 일부 클라 미설치**: 모드를 클라에 설치한 플레이어와 안 2. **서버 설치 + 일부 클라 미설치**: 모드를 클라에 설치한 플레이어와 안
한 플레이어 혼재 → 호스트 start → 클라 미설치 본인에게 "영상재생 모드 한 플레이어 혼재 → 호스트 start → 클라 미설치 본인에게 "영상재생 모드
미설치 — 본인 화면에는 영상이 표시되지 않습니다." 노란 경고. 차단 없이 미설치" + 전원에게 "필수 모드 미설치 플레이어가 있어..." 후 차단.
게임 진행. 3. **서버 + 모든 클라 설치**: 모두 정상 → start 정상 진행.
3. **서버 + 모든 클라 설치**: 모두 정상 → start 정상 진행 + 영상 표시.
4. `/scoreboard players list #server` 로 server presence 점수 확인, 4. `/scoreboard players list #server` 로 server presence 점수 확인,
`/scoreboard players list <player>` 로 client presence 점수 확인. `/scoreboard players list <player>` 로 client presence 점수 확인.

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

@@ -15,7 +15,7 @@
{ {
"type": "minecraft:plain_message", "type": "minecraft:plain_message",
"contents": { "contents": {
"text": "\n1. 정답입력시에 채팅으로 입력해주시면 됩니다.\n[ 띄어쓰기, 영어 대소문자 ]\n상관없이 입력하셔도 인식 됩니다.\n\n2. 모든 소리는 주크박스/소리 블록 채널로 조절할수 있습니다.\n\n3. 게임시작후 버튼들은 과반수(절반이상)가 눌러야 작동합니다.\n\n4. 힌트는 특수문자 제외 정답의 절반이 가려져서 나옵니다.\n힌트는 여러번 받을수 있고,\n받을때마다 가려지는 부분이 달라집니다." "text": "\n1. 정답입력시에 채팅으로 입력해주시면 됩니다.\n[ 띄어쓰기, 영어 대소문자, 특수문자 ]\n상관없이 입력하셔도 인식 됩니다.\n\n2. 모든 소리는 날씨 소리로 조절할수 있습니다.\n\n3. 게임시작후 버튼들은 과반수(절반이상)가 눌러야 작동합니다.\n\n4. 힌트는 특수문자 제외 정답의 절반이 가려져서 나옵니다.\n힌트는 여러번 받을수 있고,\n받을때마다 가려지는 부분이 달라집니다."
}, },
"width": 300 "width": 300
} }

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,7 +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

@@ -1,35 +1,41 @@
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":""}
# ---- 외부 모드 확인 ---- # ---- 외부 모드 설치 검증 ----
# * mq_chat_mod : mc_chat_answer_mod = 채팅 정답 판정에 필수인 서버 전용 # 두 모드는 성격이 달라서 검증 방식이 다름:
# 모드. 서버에서 채팅을 가로채야 퀴즈가 성립하므로 없으면 시작 차단.
# 모드가 매 server tick 마다 fake player `#server` 점수를 1 로 set.
# #
# * mq_video_mod : mc_video_player_mod = 정답 영상 재생용 선택 모드. 없어도 # * mq_chat_mod : mc_chat_answer_mod = 서버 전용 모드 (채팅 가로채기는
# 게임은 정상 진행되며 영상만 표시되지 않는다 → 차단하지 않고 경고만 출력. # 서버에서 일어남, 클라 설치 불필요). 따라서 fake player `#server`
# - `#server mq_video_mod` : 서버 컴포넌트가 매 tick 1 로 갱신. 없으면 0. # 점수를 모드가 매 server tick 마다 1 로 set. 서버에 모드가 없으면
# - `<player> mq_video_mod` : 클라 join handshake 수신 시 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 유지. # 미설치면 0 유지.
# 이렇게 분리해야 "서버 미설치"와 "특정 플레이어 클라 미설치"가 안내에서
# 1) 채팅정답 모드(필수) 서버 부재 — 전원 차단. # 구분된다.
#
# 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_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) 영상 모드(선택) 부재 — 경고만 출력하고 그대로 진행. 영상은 안 나옴. # 2) 클라이언트 측 모드 (mc_video_player_mod) 부재 — 본인 누락 안내 + 차단.
# selector `scores={X=..0}` 는 점수 미존재를 매치하지 않으므로 직전에 # selector `scores={X=..0}` 는 점수 미존재를 매치하지 않으므로 직전에
# `add @a ... 0` 으로 materialize. # `add @a ... 0` 으로 materialize. 개인 안내는 tellraw @s 직접 (mq:tellraw
# 는 내부 @a broadcast 라 부적합).
scoreboard players add @a mq_video_mod 0 scoreboard players add @a mq_video_mod 0
execute unless score #server mq_video_mod matches 1 run function mq:tellraw {"text":"영상재생 모드가 서버에 미설치 — 영상 없이 진행됩니다.","color":"yellow","msg":""} execute as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 모드 적용 후 다시 입장해주세요.","color":"red"}]
execute if score #server mq_video_mod matches 1 as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 본인 화면에는 영상이 표시되지 않습니다.","color":"yellow"}] execute if entity @a[scores={mq_video_mod=..0}] run return run function mq:tellraw {"text":"필수 모드 미설치 플레이어가 있어 시작할 수 없습니다.","color":"red","msg":""}
scoreboard players set start buttons -3 setblock ~ ~ ~ minecraft:air
function mq:quiz/stop_sound function mq:quiz/stop_sound
$scoreboard players set max_index main $(max_index) $scoreboard players set max_index main $(max_index)
scoreboard players set init main 1 scoreboard players set init main 1
# 시작 시 1..preload 번 영상 캐시 미리받기(큐 적재). schedule 로 다음 tick 에
# 실행해 max_index 설정 이후 동작 보장. 실제 다운로드는 drain 이 tick 마다 처리.
schedule function mq:videos/cache/preload 1t
dialog show @a mq:page1 dialog show @a mq:page1

View File

@@ -51,8 +51,6 @@ scoreboard objectives add score dummy {"text":"점수","bold":true}
scoreboard objectives setdisplay sidebar score scoreboard objectives setdisplay sidebar score
dialog clear @a dialog clear @a
# 정답 video URL actionbar 잔상 제거 (전체 리셋의 일부)
title @a actionbar {"text":""}
bossbar set mq:process name [{"text":"진행도: ","color": "yellow","bold": true},{"score":{"name":"index","objective": "main"},"color": "yellow","bold": true},{"text":"/","color": "yellow","bold": true},{"score":{"name":"max_index","objective": "main"},"color": "yellow","bold": true}] bossbar set mq:process name [{"text":"진행도: ","color": "yellow","bold": true},{"score":{"name":"index","objective": "main"},"color": "yellow","bold": true},{"text":"/","color": "yellow","bold": true},{"score":{"name":"max_index","objective": "main"},"color": "yellow","bold": true}]
$bossbar set mq:process max $(max_index) $bossbar set mq:process max $(max_index)
@@ -66,15 +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
function mq:videos/clear
function mq:videos/cache/clear

View File

@@ -1,8 +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":'""'}
execute as @a at @s run playsound minecraft:block.note_block.bell weather @s ~ ~ ~ 1 0.9
$stopsound @a $(source) $(namespace):bell execute as @a at @s run playsound minecraft:block.note_block.bell weather @s ~ ~ ~ 1 0.9
# 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 $(namespace):bell $(source) @s ~ ~ ~ 1 0.9
$execute as @a at @s run playsound $(namespace):bell $(source) @s ~ ~ ~ 1 0.9
$execute as @a at @s run playsound $(namespace):bell $(source) @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

@@ -1,4 +0,0 @@
# 실행한 플레이어에게 gif 페인팅 아이템을 지급한다.
# 벽에 설치하면 mq:gif variant 로 고정 배치되고(랜덤 선택 X), 리소스팩의
# musicquiz:gif 애니메이션 텍스처(.png + .png.mcmeta) 덕분에 gif 처럼 움직인다.
give @s minecraft:painting[minecraft:painting/variant="mq:gif"]

View File

@@ -1,7 +1 @@
# "Block-attached entity at invalid position" 로그 제거(MC-252934). $summon minecraft:painting $(x) $(y) $(z) {variant:"$(namespace):$(cover)",facing:$(facing)b,Tags:["mq","mq_cover"]}
# /summon 은 NBT 를 먼저 읽어(Entity.load → readAdditionalSaveData 가 block_pos 검사)
# 그 다음에 명령 좌표로 엔티티를 이동시킨다. 따라서 block_pos 만 주면 검사 시점에
# 엔티티가 기본 위치라 16블록 거리 검사를 실패해 로그가 계속 남는다.
# Pos 를 함께 넣으면 load 단계에서 위치가 먼저 설정돼 block_pos 검사를 통과한다.
# (1.20.5+ block_pos:[I; x,y,z] 정수배열 = 페인팅 자신의 블록 좌표)
$summon minecraft:painting $(x) $(y) $(z) {Pos:[$(x)d,$(y)d,$(z)d],variant:"$(namespace):$(cover)",facing:$(facing)b,block_pos:[I;$(x),$(y),$(z)],Tags:["mq","mq_cover"]}

View File

@@ -9,16 +9,15 @@
# label_color : 텍스트 색 (예 "black", "red", "#FFAA00"). 기본 "black". # label_color : 텍스트 색 (예 "black", "red", "#FFAA00"). 기본 "black".
# label_font : 텍스트 폰트 (예 "minecraft:default"). 기본 "minecraft:default". # label_font : 텍스트 폰트 (예 "minecraft:default"). 기본 "minecraft:default".
# label_scale : 텍스트 크기 (Vector3f 한 축, 3축 동일). 기본 "1.0". # label_scale : 텍스트 크기 (Vector3f 한 축, 3축 동일). 기본 "1.0".
# label_addY : 추가할 높이. 기본 "-0.1".
# #
# interaction entity 의 위치/크기와 text_display 의 위치/회전은 facing 만 # interaction entity 의 위치/크기와 text_display 의 위치/회전은 facing 만
# 보면 결정됨 — 매번 손으로 ox/oy/oz 를 적지 않는다. 실제 오프셋 테이블은 # 보면 결정됨 — 매번 손으로 ox/oy/oz 를 적지 않는다. 실제 오프셋 테이블은
# 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 with storage mq:main audio", label:"소리\n테스트", label_addY: "-0.2"} 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,39 +2,21 @@
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")
# source — /playsound 채널. stopsound 와 동일해야 함. 노래는 "record" 채널로 # source — /playsound 채널. stopsound 와 동일해야 함. 노래는 "player" 채널로
# 재생 (주크박스/소리 블록 채널 슬라이더로 음량 제어). # 재생 (음성/플레이어 채널 슬라이더로 음량 제어). 타이머/UI 비프는
# 별도로 weather 채널 사용.
# volume — 기본 음량. 곡별 override 는 init/songs.mcfunction 의 volume 필드 사용 # volume — 기본 음량. 곡별 override 는 init/songs.mcfunction 의 volume 필드 사용
# pitch — 1.0 = 원본 속도 # pitch — 1.0 = 원본 속도
data modify storage mq:main audio set value {namespace: "musicquiz", source: "record", volume: 1.0, pitch: 1.0} data modify storage mq:main audio set value {namespace: "musicquiz", source: "player", volume: 1.0, pitch: 1.0}
# 정답 페인팅 — 데이터팩의 mq:cover_NN painting_variant (텍스처는 리소스팩 musicquiz:cover_NN) # 정답 페인팅 — 데이터팩의 mq:cover_NN painting_variant (텍스처는 리소스팩 musicquiz:cover_NN)
# 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: 2782, y: 88, z: 5979, facing: 2b} data modify storage mq:main image set value {namespace: "mq", x: 144, y: 84, z: -261, facing: 0b}
# 정답 영상 — 모드의 영상 재생 관련
# namespace — 영상 아이디를 제외한 주소 (마지막 / 는 자동으로 제거됨)
# x,y,z — 영상 블럭 좌표 (벽면 앞쪽 블록 위치)
# w,h — 영상 가로세로 크키 (왼쪽아래 기준)
# facing — 영상이 바라보는 방향: south / west / north / east
# sound — 영상 소리 설정 (0~100, 음소거: -1)
# preload — 게임 시작 시 미리 받아둘 영상 캐시 갯수 (1번부터 N번까지).
# 재생 중에도 항상 N개 앞까지 미리 받아둠. 0 이면 미리받기 끔(매번 URL 재생)
# keep — 캐시 유지 갯수(FIFO). 이 값을 넘기면 가장 먼저 받은 캐시부터 삭제.
# 메모리 관리용이므로 preload 보다 크거나 같게 설정 권장
data modify storage mq:main video set value { \
namespace: "https://video.tkrmagid.kr/api/video//s3-2", \
cmd_x: 2769, cmd_y: 73, cmd_z: 5965, \
x: 2731, y: 96, z: 6034, facing: "north", w: 25,h: 14, \
sound: -1, \
preload: 5, \
keep: 10 \
}
# 곡 개수 max_index 는 init/songs.mcfunction 의 길이로 자동 계산됨 # 곡 개수 max_index 는 init/songs.mcfunction 의 길이로 자동 계산됨

View File

@@ -1,62 +1,59 @@
# 곡 한 개 = 한 줄. # 곡 한 개 = 한 줄.
# 필수 — title, author, alias, description # 필수 — title, author, alias, volume
# 선택 — volume (이 곡만의 /playsound 음량. 미지정시 init/config.mcfunction # volume: /playsound 음량. 1.0 = 기본. 곡마다 음량 조절 가능.
# 의 audio.volume 사용)
# video (정답 공개 시 actionbar 에 흰색으로 표시되는 영상 URL. 미지정시
# 표시 안 함)
# 곡 순서가 리소스팩의 track_NN / cover_NN 인덱스와 1:1 매칭된다. # 곡 순서가 리소스팩의 track_NN / cover_NN 인덱스와 1:1 매칭된다.
# 예) {volume:0.5, title:"Quiet Song", author:"...", alias:[...], description:"...", video:"..."} # 예) {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 {volume:0.7, title:"GTA V", author:"Welcome to Los Santos Intro/Theme", alias:["Grand Theft Auto","Grand Theft Auto 5","Grand Theft Auto V","GTA","GTA 5"], description:"GTA V의 인트로 테마곡이다. 로스 산토스에 입성하는 순간의 어두우면서도 힙한 분위기를 형성하는 시그니처 사운드트랙이다.", video:"https://youtu.be/QkkoHAzjnUs"} 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 {volume:0.7, title:"FIFA 2000", author:"It's Only Us", alias:["FIFA"," 2000",""], description:"영국 가수 Robbie Williams의 곡 \"It's Only Us\". EA에서 FIFA 2000의 사운드트랙으로 라이선스해 인트로 곡으로 삽입한 노래이다. 피파가 큰 인기를 끌게 되며 게임에 삽입곡으로 분위기를 만드는 전통을 이어간 초기 사례.", video:"https://youtu.be/YznY5_PQZ3Y"} 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 {volume:1.27, title:"테일즈런너", author:"트레이닝 시리즈 BGM", alias:[], description:"이 곡은 테일즈런너의 트레이닝 시리즈 BGM으로, 가벼운 리듬감의 곡이 기본적인 맵인 트레이닝 시리즈의 분위기와 어울리며 많은 이들의 뇌리에 각인되었다.", video:"https://youtu.be/gfswv3n6sLw"} 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 {volume:0.5, title:"ARK : Survival Evolved", author:"Main Theme", alias:["ARK Survival Evolved"," "], description:"작곡가 Gareth Coker가 작곡하고 Abbey Road 스튜디오에서 93인조 필하모니아 오케스트라로 녹음된 곡으로, 광활한 공룡 생존 세계의 웅장함과 신비로움을 담은 합창과 오케스트라가 인상적이다.", video:"https://youtu.be/FW9vsrPWujI"} 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 {volume:0.6, title:"League of Legends", author:"Challengers", alias:[" "], description:"2015년에 라이엇 게임즈가 발매한 앨범 \"The Music of League of Legends Vol. 1\"의 수록곡이다. 라이엇 게임즈가 LoL의 세계관과 e스포츠 문화를 음악으로 표현하기 위해 모은 정식 게임 OST 컴필레이션 중 한 트랙이며, LoL의 본질을 담은 곡이다. 과거에는 인 게임에 재생되어 익숙한 곡이었으나, 현재는 재생되지 않는다.", video:"https://youtu.be/zF5Ddo9JdpY"} 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 {volume:0.5, title:"카트라이더", author:"상점 BGM", alias:["Kart Rider"], description:"넥슨의 국민 레이싱 게임이었던 카트라이더의 상점 BGM이다. 게임을 해봤던 게이머라면 모를 수가 없을 정도의 경쾌하고 친숙한 멜로디로 한국 게이머들에게 추억의 곡으로 자리 잡았다.", video:"https://youtu.be/Ie4tEpNuWps"} data modify storage mq:main songs append value {title:"OVERDRIVE", author:"TWS", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:1.54, title:"The Legend of Zelda", author:"Main Theme", alias:[" "], description:"젤다의 전설: 신들의 트라이포스의 메인 테마. 콘도 코지가 작곡한 젤다 시리즈의 영원한 클래식 명곡이다.", video:"https://youtu.be/a9WLI25xb8Q"} data modify storage mq:main songs append value {title:"눈물참기", author:"QWER", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.53, title:"크레이지 아케이드", author:"Camp BGM", alias:[], description:"크레이지 아케이드 맵 캠프 시리즈의 BGM이다. 빠른 템포와 단순한 반복 멜로디가 인상적이다.", video:"https://youtu.be/_Gj9ef-Jdd8"} data modify storage mq:main songs append value {title:"깨어", author:"tripleS", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.54, title:"메이플스토리", author:"에레브 수련의 숲 BGM", alias:["Maplestory"], description:"메이플스토리 에레브 지역의 BGM이다. 시그너스 기사단의 시작 지역으로, 빗방울이 떨어지는 듯한 서정적이고 신비로운 분위기의 명곡으로 자리 잡았다.", video:"https://youtu.be/syNANSJN_xc"} data modify storage mq:main songs append value {title:"like JENNIE", author:"제니", alias:[" "," "," "," "], volume:1.0}
data modify storage mq:main songs append value {volume:1.72, title:"Minecraft", author:"C418 - Mice on Venus", alias:[""], description:"마인크래프트 C418의 대표곡으로, 게임을 플레이하지 않은 사람도 알 정도로 유명한 명상적이고 서정적인 피아노 곡. 이 곡을 들으면 괜히 추억에 잠길 수도 있게 된다.", video:"https://youtu.be/MmB9b5njVbA"} 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 {volume:0.35, title:"던전 앤 파이터", author:"Purgatorium", alias:[], description:"퍼가토리엄 지역을 통과하는 데 사용된 테마곡이며, 던전앤파이터 하면 이 곡이 떠오를 정도로 많은 던전앤파이터 유저들에게 추억의 곡으로 알려져 있다. 특히 타 게임 \"레바의 모험\"에서도 주로 사용되며 던전앤파이터를 해보지 않은 게이머들에게도 익숙하게 느껴질 수 있다.", video:"https://youtu.be/B8_VeJLH80I"} 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 {volume:0.55, title:"DJMAX RESPECT V", author:"Lobby Main Theme", alias:["DJMAX RESPECT 5","DJMAX RESPECT"," V"," 5"," "], description:"DJMAX RESPECT V의 메인 메뉴 BGM이다. 시리즈 특유의 세련된 일렉트로닉 사운드가 매력적이다.", video:"https://youtu.be/hZ0jXmDv3Ac"} data modify storage mq:main songs append value {title:"SIGN", author:"izna", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.52, title:"소닉", author:"Green Hill Zone", alias:["Sonic"], description:"소닉 더 헤지혹의 상징과도 같은 BGM이다. 1991년 첫 작품부터 시작된 SEGA의 대표적인 BGM으로, 게임 음악 역사에 한 획을 그었다.", video:"https://youtu.be/K7-kl5DHLpU"} 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 {volume:0.77, title:"Apex 레전드", author:"Main Theme", alias:[" ","Apex Legends"], description:"에이펙스 레전드의 메인 테마곡. 박진감 넘치는 배틀로얄의 긴장감과 히어로물 같은 웅장함을 결합한 곡이다. 특히 곡 중간 부분의 특유의 멜로디가 인상적이다.", video:"https://youtu.be/7M4Vds3fI9E"} 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 {volume:0.5, title:"디아블로 2", author:"트리스트럼 BGM", alias:["","DIABLO 2","DIABLO"], description:"디아블로 2의 트리스트럼 마을 BGM이다. 시리즈를 대표하는 기타 선율로, 디아블로 1로부터 이어진 황량하고 우울한 분위기의 명곡이다.", video:"https://youtu.be/84Tr5rCVRgM"} data modify storage mq:main songs append value {title:"0+0", author:"한로로", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.5, title:"포켓몬스터", author:"등화도시 BGM", alias:[" "," ORAS"], description:"포켓몬스터 오메가루비/알파사파이어의 등화도시 BGM이다. 도시의 활기와 화려함을 담은 경쾌한 리메이크 버전이다. 쇼츠에도 많이 사용되며 포켓몬스터 BGM 중에 인기 있는 곡에 속한다.", video:"https://youtu.be/bpu5ofLNt0w"} 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 {volume:0.65, title:"슈퍼 마리오 64", author:"Main Theme", alias:[" ","Super Mario 64","Super Mario"], description:"닌텐도의 명작 슈퍼마리오 64의 메인 테마곡이다. 3D 마리오 시리즈의 역사적 시작을 알린 곡이다. 닌텐도 게임 음악사에서 빼놓을 수 없는 명곡 중 하나이다.", video:"https://youtu.be/Ujir2X1D_yw"} 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 {volume:0.65, title:"Elden Ring", author:"Malenia, Blade of Miquella", alias:[" "], description:"엘든 링의 최강 보스 중 하나인 말레니아, 미켈라의 칼날 전투 BGM이다. 소프라노 보컬과 비장한 오케스트라가 어우러진 보스 BGM의 정점으로, 엘든 링을 클리어해본 유저라면 이 곡이 친숙하게 느껴질 수 있다.", video:"https://youtu.be/8McaLMSQRBk"} data modify storage mq:main songs append value {title:"Flower", author:"오반", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.7, title:"별의 커비", author:"Green Greens", alias:["Kirby Super Star"], description:"커비 시리즈를 대표하는 Green Greens BGM. 첫 스테이지 BGM으로 시작해 시리즈 전반에 걸쳐 다양하게 편곡되는 커비의 시그니처 테마이다.", video:"https://youtu.be/aYjFa0dVAjo"} 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 {volume:0.51, title:"원신", author:"Genshin Impact Main Theme", alias:["Genshin Impact"], description:"원신의 메인 테마곡이다. HOYO-Mix가 작곡한 웅장하고 환상적인 오케스트라 곡으로, 원신을 가장 잘 표현한 곡으로 평가받고 있다.", video:"https://youtu.be/poaXbXgTWx0"} 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 {volume:0.86, title:"Slay The spire 2", author:"Main Menu Theme", alias:[" 2"," "], description:"인기를 끌고 있는 인디 덱빌딩 로그라이크 게임 슬레이 더 스파이어 2의 메인 메뉴 테마곡이다. 신비로운 탑을 오르는 여정의 분위기를 담았다.", video:"https://youtu.be/caEYvo6c8oU"} 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 {volume:0.83, title:"Arc Raiders", author:"Lobby Music - Track 01", alias:[" "], description:"익스트랙션 슈터 장르인 아크 레이더스의 로비 음악이다. 아크 레이더스는 출시 초기 참신한 컨셉과 완성도 높은 게임성으로 큰 인기를 끌었으며 현재도 꾸준한 인기를 유지하고 있다. 게임을 켜자마자 나오는 포스트 아포칼립스 분위기의 긴장감 있는 사운드가 특징이다.", video:"https://youtu.be/xuftkDxjGT4"} data modify storage mq:main songs append value {title:"BEEP", author:"izna", alias:["","",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.48, title:"FIFA 온라인 3", author:"Yeah Yeah Yeah", alias:["FIFA ","FC 3","FC "," 3"," "], description:"영국 DJ Jax Jones가 2015년 7월 발매한 댄스/하우스 싱글로, 게임 OST로 작곡된 곡은 아니지만 클럽/스포츠 게임 등에서 BGM이나 라이선스 음악으로 자주 활용된다. 경쾌한 비트와 멜로디가 인상적이다.", video:"https://youtu.be/Pbnh06kCgVk"} 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 {volume:0.55, title:"Terraria", author:"Underground BGM", alias:[""], description:"샌드박스 게임인 테라리아의 지하 BGM이다. 지하 탐험의 신비로운 분위기를 담았다.", video:"https://youtu.be/HHNLfxBxcvo"} 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 {volume:0.65, title:"Project Zomboid", author:"Main Menu Theme", alias:[" "], description:"좀비 생존 게임인 프로젝트 좀보이드의 메인 메뉴 BGM. 게임을 켜게 되면 이 곡이 재생되며 게임에 황량하고 외로운 분위기를 더해준다.", video:"https://youtu.be/pU150792vtY"} data modify storage mq:main songs append value {title:"빌려온 고양이", author:"ILLIT", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.53, title:"Undertale", author:"His Theme", alias:[""], description:"언더테일의 명곡 중 하나. 게임 후반부의 감동적인 순간을 책임지는 Toby Fox의 대표곡이다.", video:"https://youtu.be/1Hojv0m3TqA"} 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 {volume:0.71, title:"Battlefield 4", author:"Main Theme", alias:["Battlefield"," 4",""], description:"배틀필드 4의 공식 메인 테마곡이다. DICE의 시그니처 시리즈 사운드로, 전쟁 영화 같은 웅장함을 담았다. 노래의 웅장함 덕분에 게임 BGM뿐만 아니라 여러 웅장하고 전투적인 영상에 자주 쓰이는 곡이다.", video:"https://youtu.be/vupPwLJ1zkE"} data modify storage mq:main songs append value {title:"시작의 아이", author:"마크툽", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.73, title:"Stardew Valley", author:"Stardew Valley Overture", alias:[" "], description:"스타듀밸리의 오프닝 테마이다. 따뜻하고 향수 어린 멜로디로, 시골 농장 생활의 시작을 알린다.", video:"https://youtu.be/ot7uXNQskhs"} data modify storage mq:main songs append value {title:"STYLE", author:"Hearts2Hearts", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.7, title:"Rainbow Six Siege", author:"영사관 지하 라디오", alias:[" "], description:"레인보우 식스 시즈의 영사관 맵 지하에서 재생되는 라디오 BGM이다. 이 게임을 플레이해본 게이머라면 영사관 맵 지하에 들어서는 순간 들리는 이 익숙한 라디오 BGM을 잊을 수 없을 것이다.", video:"https://youtu.be/yX6F6764sMQ"} data modify storage mq:main songs append value {title:"Good Goodbye", author:"화사", alias:["굿 굿"," "," "], volume:1.0}
data modify storage mq:main songs append value {volume:0.63, title:"StarCraft", author:"Terran Theme 3", alias:[""], description:"대한민국 전통 민속놀이 스타크래프트의 테란 종족 3번째 트랙 곡이다. 우주적이고 전투적인 분위기를 잘 담은 곡으로 한국 e스포츠와 함께 자란 세대에게 정말 깊은 향수를 자극한다. 특히 테란 종족 1번째 트랙이 대표적이다.", video:"https://youtu.be/Cqh37RlJ32Y"} data modify storage mq:main songs append value {title:"너에게 닿기를", author:"10CM", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.81, title:"모두의 마블", author:"월드맵 BGM", alias:[], description:"넷마블의 모바일 게임 모두의 마블의 월드맵 BGM. 신나는 분위기로 여행 콘셉트를 표현한다.", video:"https://youtu.be/FMJe3L4amNE"} 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 {volume:0.7, title:"Blue Archive", author:"Constant Moderato", alias:[" "], description:"블루 아카이브의 메인 테마곡이다. 잔잔하고 청량하고 산뜻한 분위기로 인트로를 알리며 중반부 신나는 멜로디와 비트가 인상 깊다. 학원 도시 키보토스의 일상을 잘 표현하는 시리즈의 시그니처 곡이다.", video:"https://youtu.be/YHy8JmWX3kw"} 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 {volume:0.51, title:"튀어나와요 동물의 숲", author:"In Kapp'n's Bus", alias:[" "], description:"동물의 숲 시리즈의 캅파 보트 투어 BGM. 무인도 여행의 즐거움을 담은 곡이다.", video:"https://youtu.be/3fKRexrC76c"} 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 {volume:0.62, title:"VALORANT", author:"WATCH", alias:[""], description:"라이엇 게임즈의 FPS 게임인 발로란트의 공식 음악이다. 챔피언스 투어 등 e스포츠 이벤트와 함께 공개된 곡이지만, 인 게임 스킨 테마곡으로 쓰이며 많은 게이머들에게 친숙하게 되었다.", video:"https://youtu.be/RCUI9q99qus"} 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 {volume:0.66, title:"Civilization VI", author:"Sogno di Volare", alias:["Civilization 6"," VI"," 6"], description:"문명 6의 메인 테마곡이다. 아카데미상 후보에 오른 작곡가 Christopher Tin이 작곡한 만큼, 이탈리아어 합창곡의 분위기가 문명 시리즈 음악의 정점을 알린다.", video:"https://youtu.be/uULVgaeHPqg"} data modify storage mq:main songs append value {title:"Gabriela", author:"KATSEYE", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.67, title:"배틀그라운드", author:"Old Main Theme", alias:[], description:"배틀그라운드의 구 메인 로비 BGM이다. 배틀그라운드가 막 출시한 시점 게임을 켜게 되면 항상 듣게 되는 곡이었으며, 배틀로얄 장르를 대중화시킨 작품의 긴장감 있는 테마가 인상 깊다.", video:"https://youtu.be/kp3YL3QoPEE"} 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 {volume:0.5, title:"버블파이터", author:"대기방 BGM", alias:["Bubble Fighter"], description:"넥슨의 캐주얼 슈팅 게임 버블파이터의 대기방 BGM이다. 친숙하고 귀여운 분위기의 곡이 인상적이다.", video:"https://youtu.be/xpPKJMpRkY8"} data modify storage mq:main songs append value {title:"뛰어", author:"BLACKPINK", alias:["jump"], volume:1.0}
data modify storage mq:main songs append value {volume:1.25, title:"Bubble Bobble", author:"Main Theme", alias:[""], description:"추억의 클래식 아케이드 게임 버블보블의 메인 테마곡이다. 짧지만 강렬한 중독성으로 80~90년대 오락실 세대의 추억을 자극하는 명곡이다.", video:"https://youtu.be/Hhh_yY3fTb8"} data modify storage mq:main songs append value {title:"CHANEL", author:"Tyla", alias:["",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.74, title:"냥코 대전쟁", author:"전투 테마 1", alias:[], description:"모바일 게임 냥코 대전쟁의 전투 BGM이다. 귀엽지만 중독성 있는 멜로디가 특징이며, 게임을 플레이해봤다면 익숙할 것이다.", video:"https://youtu.be/2ay66PWuw6o"} 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 {volume:0.55, title:"SEPHIRIA", author:"토끼마을 BGM", alias:[""], description:"인디 RPG 세피리아의 토끼마을 BGM이다. 평화로운 마을의 분위기를 담은 곡.", video:"https://youtu.be/Ls9h1MZ0mFE"} data modify storage mq:main songs append value {title:"earthquake", author:"지수", alias:["","",""," "], volume:1.0}
data modify storage mq:main songs append value {volume:0.8, title:"산나비", author:"끝났다는 것은 다시 시작된다는 것을", alias:["SANABI"], description:"한국 인디 게임인 산나비의 엔딩곡이다. 감동적인 스토리의 여운을 그대로 전달하는 명곡으로 평가받는다. 산나비를 플레이해본 유저라면 이 노래를 듣고 감동의 눈물을 흘릴 수밖에 없다.", video:"https://youtu.be/wZ6wx0rSL8M"} data modify storage mq:main songs append value {title:"윽!", author:"염따", alias:[""], volume:1.0}
data modify storage mq:main songs append value {volume:1.9, title:"쿠키런", author:"에피소드 1 Main Theme", alias:[], description:"모바일 게임 쿠키런의 첫 에피소드 BGM이다. 경쾌하고 귀여운 분위기로 쿠키들의 오븐 탈출을 잘 표현한 곡이다.", video:"https://youtu.be/_m6SEk_yK8A"} 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 {volume:0.89, title:"끄투", author:"로비 BGM", alias:[], description:"한국 끝말잇기 게임 끄투의 구 로비 BGM이다. 한국 웹 게임 문화를 대표하는 곡 중 하나.", video:"https://youtu.be/1vTkng-RMCk"} data modify storage mq:main songs append value {title:"멸종위기사랑", author:"이찬혁", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.71, title:"Geometry Dash", author:"Stay Inside Me", alias:[" "], description:"지오메트리 대시의 사운드트랙이다. 지오메트리 대시는 모르는 맵을 클리어하기 위해서 연습 모드를 플레이할 수밖에 없는데, 이 곡이 연습 모드에서 반복 재생되며 많은 이들의 뇌리에 집어넣어졌다.", video:"https://youtu.be/WkkBkBcVzK8"} 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 {volume:0.98, title:"레이튼 교수와 이상한 마을", author:"수수께끼", alias:[], description:"닌텐도 DS의 명작 추리/퍼즐 게임 레이튼 교수와 이상한 마을의 OST이다. 아코디언 선율의 특징적인 유럽풍 분위기의 곡.", video:"https://youtu.be/MZjBpjmMccY"} 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 {volume:0.62, title:"TEKKEN 7", author:"Infinite Azure - Round 1", alias:["TEKKEN"," 7",""], description:"철권 7의 인피니트 아주르 스테이지 BGM. 시리즈 전통의 일본풍 도시 야경 스테이지의 세련된 EDM 트랙으로, 주로 인 게임에서 콤보 연습을 할 때 자주 사용하는 맵이므로 플레이해본 게이머들에겐 익숙한 곡이다.", video:"https://youtu.be/5Lw3tg40DG8"} 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 {volume:0.6, title:"엘소드", author:"Login Screen 1", alias:[], description:"넥슨 게임 엘소드의 로그인 화면 BGM. 중세 판타지 전투의 분위기를 가볍게 표현했다.", video:"https://youtu.be/B-M0f1lIB5g"} data modify storage mq:main songs append value {title:"XOXZ", author:"IVE", alias:[" "," ",""], volume:1.0}
data modify storage mq:main songs append value {volume:0.77, title:"Clash Royale", author:"Menu A", alias:[" "], description:"슈퍼셀의 모바일 게임 클래시 로얄의 메뉴 BGM이다. 게임을 해봤다면 정말 정말 익숙한 곡이다.", video:"https://youtu.be/_hNxfiXmeAE"} data modify storage mq:main songs append value {title:"여름이었다", author:"H1-KEY", alias:[], volume:1.0}
data modify storage mq:main songs append value {volume:0.58, title:"OverWatch", author:"환상의 겨울나라 BGM", alias:[""], description:"오버워치 환상의 겨울나라 이벤트 BGM으로, 크리스마스 시즌 한정 이벤트의 따뜻한 분위기를 담았다. 그 시절 겨울에도 오버워치를 플레이해본 유저라면 추억이 떠오를 수밖에 없는 향수 같은 곡이다.", video:"https://youtu.be/66h40e_0_Q8"} 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 {volume:0.77, title:"Cyberpunk 2077", author:"I Really Want to Stay at Your House", alias:["Cyberpunk"," 2077",""], description:"사이버 펑크 2077의 OST이자 애니메이션 사이버펑크: 엣지러너의 엔딩곡인 이 곡은 게임 OST이긴 하나, 애니메이션에서 감성적인 마무리를 장식하며 전 세계적으로 폭발적인 인기를 끌게된 곡이다. 그와 자연스레 사이버펑크 2077의 정서를 대표하는 노래가 되었다. 죽음이란게 가볍게 여겨지는 차가운 사이버펑크 세상속에서 순수한 순애 분위기의 곡으로, 큰 여운이 남는 것이 특징.", video:"https://youtu.be/ecO_xfm8JD4"} 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 +1,8 @@
data modify storage mq:main answer set value {title:"", author:"", alias:[], description:""} data modify storage mq:main answer set value {title:"", author:"", alias:[]}
data merge storage func:temp {} data merge storage func:temp {}
data merge storage mq:tmp {} data merge storage mq:tmp {}
function mq:init/config function mq:init/config
function mq:videos/normalize
function mq:init/songs function mq:init/songs
function mq:init/buttons function mq:init/buttons
function mq:init/triggers function mq:init/triggers
@@ -23,11 +22,9 @@ scoreboard objectives add answer dummy
scoreboard objectives add leave_game custom:leave_game scoreboard objectives add leave_game custom:leave_game
# 외부 모드 존재 확인용 점수. # 외부 모드 존재 확인용 점수.
# mq_chat_mod : 채팅 정답 판정 필수 모드(mc_chat_answer_mod, 서버 전용). # mq_chat_mod : 서버 전용 모드(mc_chat_answer_mod). 모드가 매 server tick
# 모드가 매 server tick 마다 fake player `#server` 점수를 1 로 set. 서버에 # 마다 fake player `#server` 점수를 1 로 set. 모드가 서버에 없으면 0 유지.
# 없으면 0 유지 → start 차단. # mq_video_mod : 클라이언트 모드(mc_video_player_mod). 클라 join 시 서버로
# mq_video_mod : 정답 영상 재생용 선택 모드(mc_video_player_mod). 없어도
# 게임은 진행되고 영상만 안 나온다(start 는 경고만). 클라 join 시 서버로
# handshake payload 전송 → 서버 측 모드가 해당 플레이어 점수를 1 로 set. # handshake payload 전송 → 서버 측 모드가 해당 플레이어 점수를 1 로 set.
# 클라에 모드가 없으면 0 유지. (login.mcfunction 에서 플레이어별 0 초기화.) # 클라에 모드가 없으면 0 유지. (login.mcfunction 에서 플레이어별 0 초기화.)
scoreboard objectives remove mq_chat_mod scoreboard objectives remove mq_chat_mod
@@ -48,5 +45,3 @@ bossbar add mq:process [{"text":"진행도: ","color": "yellow","bold": true},{"
function mq:commands/stop with storage mq:main function mq:commands/stop with storage mq:main
function mq:players/login with storage mq:main spawn function mq:players/login with storage mq:main spawn
function mq:videos/cache/clear

View File

@@ -1,13 +1,12 @@
tag @s add player tag @s add player
scoreboard players reset @s leave_game scoreboard players reset @s leave_game
# 외부 모드 확인 점수 초기화 (per-player 대상만). # 외부 모드 검증 점수 초기화 (per-player 검증 대상만).
# mq_video_mod : 영상 모드(mc_video_player_mod, 선택)는 join 시 handshake # mq_video_mod : 클라이언트 모드(mc_video_player_mod) 가 join 시 handshake
# payload 를 서버로 보내면 서버 모드가 해당 플레이어 점수를 1 로 set 한다. # payload 를 서버로 보내면 서버 모드가 해당 플레이어 점수를 1 로 set 한다.
# 여기서 0 으로 미리 깔아 두면 handshake 가 없는 플레이어는 0 유지 → # 여기서 0 으로 미리 깔아 두면 handshake 가 없는 플레이어는 0 유지 →
# start 에서 "영상 안 보임" 경고 대상이 된다(차단은 아님). handshake 가 # start 가드 차단. handshake 가 오면 곧바로 1 로 갱신됨.
# 오면 곧바로 1 로 갱신됨. # mq_chat_mod 는 서버 전용 모드라 fake player(#server) 로 검증 — per-player
# mq_chat_mod 는 서버 전용 모드라 fake player(#server) 로 확인 — per-player
# 초기화 불필요. # 초기화 불필요.
scoreboard players set @s mq_video_mod 0 scoreboard players set @s mq_video_mod 0

View File

@@ -4,8 +4,7 @@ scoreboard players set @s answer 2
function mq:tellraw {"text":"","color":"black","msg":""} function mq:tellraw {"text":"","color":"black","msg":""}
function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답: ","color": "aqua"},{"storage":"mq:main","nbt":"answer.title","color": "yellow","bold": true}]} function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답: ","color": "aqua"},{"storage":"mq:main","nbt":"answer.title","color": "yellow","bold": true}]}
function mq:tellraw {"text":"","color":"black",msg:[{"text":"제목: ","color":"aqua"},{"storage":"mq:main","nbt":"answer.author","color": "yellow","bold": true}]} function mq:tellraw {"text":"","color":"black",msg:[{"text":"가수: ","color":"aqua"},{"storage":"mq:main","nbt":"answer.author","color": "yellow","bold": true}]}
function mq:tellraw {"text":"","color":"black",msg:[{"text":"설명: ","color":"aqua"},{"storage":"mq:main","nbt":"answer.description","color": "yellow","bold": false}]}
execute if score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"text":"스킵","color": "yellow","bold": true}]} execute if score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"text":"스킵","color": "yellow","bold": true}]}
execute unless score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"selector":"@s","color": "yellow","bold": true}]} execute unless score skip buttons matches -2 run function mq:tellraw {"text":"","color":"black",msg:[{"text":"정답자: ","color": "aqua"},{"selector":"@s","color": "yellow","bold": true}]}
function mq:tellraw {"text":"","color":"black",msg:[{"text": "( 15초뒤 다음문제로 넘어갑니다. )","color": "gray"}]} function mq:tellraw {"text":"","color":"black",msg:[{"text": "( 15초뒤 다음문제로 넘어갑니다. )","color": "gray"}]}
@@ -31,4 +30,3 @@ scoreboard players set replay buttons -3
scoreboard players set timer main 1 scoreboard players set timer main 1
function mq:images/show function mq:images/show
function mq:videos/show

View File

@@ -1,2 +1 @@
$stopsound @a $(source)
$execute as @a at @s run playsound $(namespace):$(track) $(source) @s ~ ~ ~ $(volume) $(pitch) $execute as @a at @s run playsound $(namespace):$(track) $(source) @s ~ ~ ~ $(volume) $(pitch)

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

@@ -2,9 +2,4 @@ data modify storage mq:tmp playsound set from storage mq:main audio
data modify storage mq:tmp playsound.track set from storage mq:main answer.track data modify storage mq:tmp playsound.track set from storage mq:main answer.track
# 곡 단위 volume override — songs[i].volume 가 없으면 audio.volume 그대로 유지 (no-op) # 곡 단위 volume override — songs[i].volume 가 없으면 audio.volume 그대로 유지 (no-op)
data modify storage mq:tmp playsound.volume set from storage mq:main answer.volume data modify storage mq:tmp playsound.volume set from storage mq:main answer.volume
function mq:quiz/macro/play_sound with storage mq:tmp playsound function mq:quiz/macro/play_sound with storage mq:tmp playsound
# volume 테스트 (replay 이용)
# data modify storage mq:main answer.volume set value 0.5
# function mq:quiz/play_sound

View File

@@ -18,4 +18,3 @@ execute if score index main matches 1..9 run data modify storage mq:tmp pad set
execute unless score index main matches 1..9 run data modify storage mq:tmp pad set value "" execute unless score index main matches 1..9 run data modify storage mq:tmp pad set value ""
function mq:quiz/setanswer function mq:quiz/setanswer
function mq:videos/cache/add

View File

@@ -1,6 +1,5 @@
# 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, label_addY # 매크로 인자(mq:tmp.btn): n, x, y, z, f, c, label, label_color, label_font, label_scale
# buttons 점수 상태: # buttons 점수 상태:
# ..-2 : 비활성 (버튼 블록 제거, interaction 응답 차단) # ..-2 : 비활성 (버튼 블록 제거, interaction 응답 차단)
# -1 : 초기화 단계 (버튼 블록 + interaction × 3 + text_display 보장 후 0) # -1 : 초기화 단계 (버튼 블록 + interaction × 3 + text_display 보장 후 0)
@@ -27,21 +26,15 @@
# 인접: 중심 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.
# #
# ---- 깊이축: 블록 면 바로 바깥, 플레이어 쪽 (이중 트리거 방지) ---- # ---- 깊이축: 블록 면 바로 바깥 (이중 트리거 방지) ----
# interaction 박스가 stone_button hitbox 와 겹치면 한 번 클릭에 interaction # interaction 박스가 stone_button hitbox 와 겹치면 한 번 클릭에 interaction
# 도 발화하고 stone_button 도 vanilla 클릭으로 인식되어 powered=true 애니 # 도 발화하고 stone_button 도 vanilla 클릭으로 인식되어 powered=true 애니
# 메이션이 같이 일어남. → interaction 박스를 버튼 머리 바깥쪽 (플레이어 # 메이션이 같이 일어남. → interaction 박스를 버튼 바깥쪽으로 한 두께
# 측) 으로 한 두께 (0.125) 만큼 밀어 ray 가 stone_button 에 닿기 전에 # (0.125) 만큼 ray 가 stone_button 에 닿기 전에 interaction 에서 멈추게.
# interaction 에서 멈추게. # south : 깊이 z 중심 = -0.0625 (interaction z ∈ [-0.125, 0], 버튼 z ∈ [0, 0.125])
# # north : 깊이 z 중심 = 1.0625 (interaction z ∈ [1, 1.125], 버튼 z ∈ [0.875, 1])
# 주의: facing 은 "버튼 머리 normal 방향" = 플레이어가 보는 방향. # east : 깊이 x 중심 = -0.0625
# south 면 머리 +z 향함, 벽은 -z 쪽. 따라서 플레이어 쪽 = +z = interaction # west : 깊이 x 중심 = 1.0625
# 을 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 보정 회피 ---- # ---- positioned 의 .5 보정 회피 ----
# MC 의 vec3 인자는 정수만 쓰면 자동으로 +0.5 보정됨 (블록 중심으로 잡힘). # MC 의 vec3 인자는 정수만 쓰면 자동으로 +0.5 보정됨 (블록 중심으로 잡힘).
@@ -49,76 +42,65 @@
# 을 더하면 박스 전체가 0.5 칸 어긋남. $(x).0 $(y).0 $(z).0 처럼 decimal # 을 더하면 박스 전체가 0.5 칸 어긋남. $(x).0 $(y).0 $(z).0 처럼 decimal
# 형태로 넘기면 보정 없이 정확한 블록 origin (minimal corner) 이 됨. # 형태로 넘기면 보정 없이 정확한 블록 origin (minimal corner) 이 됨.
# #
# ---- text_display 위치 (버튼 바로 아래 같은 벽면에 가운데 정렬) ---- # ---- text_display 위치 (버튼 바로 아래 같은 벽면에 부착) ----
# 버튼 아래 블록의 같은 벽 (visible 면, 플레이어 쪽) 에 살짝 띄워 부착. # 같은 벽 (button 의 머리 normal 반대편 블록) 의 visible 면에 살짝 띄워
# 가로축: ~0.5 (block 가로 중심, alignment=center 기본값과 합쳐져서 라벨 # 부착. 텍스트 entity Y 는 텍스트 baseline 근방 → 아래 블록 바닥에 두면
# 자체도 수평 중앙). # 텍스트가 그 블록 안에 위로 솟아남.
# 세로축: text_display 의 entity Y 는 텍스트 윗변 — 아래로 자람. ~-0.25 # south : ~0.5 ~-1 ~0.01 yaw 0 (head 가 +z 방향 → 벽 +z=0.01 살짝 띄움)
# 로 두면 텍스트 윗변이 Y-0.25 (버튼 바로 아래), 한 줄(기본 ~0.5 블록 높이) # north : ~0.5 ~-1 ~0.99 yaw 180
# 이 Y-0.75 까지 내려와 버튼 아래 한 칸 벽면 [Y-1, Y] 의 위쪽 절반에 # east : ~0.01 ~-1 ~0.5 yaw -90
# 자리잡음 — 시각적으로 버튼 바로 밑 가운데 라벨. # west : ~0.99 ~-1 ~0.5 yaw 90
# 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:1b} $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.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:1b} $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.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:1b} $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.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,$(label_addY)f,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).0 $(y).0 $(z).0 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(+1.0625, 블록 면 바깥), 가로축=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:1b} $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.375 ~0.375 ~1.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:"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:1b} $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.375 ~1.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:"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:1b} $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.625 ~0.375 ~1.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:"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,$(label_addY)f,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).0 $(y).0 $(z).0 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:1b} $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.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:1b} $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.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:1b} $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.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,$(label_addY)f,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).0 $(y).0 $(z).0 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(+1.0625, 블록 면 바깥), 가로축=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:1b} $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 ~1.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:"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:1b} $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 ~1.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:"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:1b} $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 ~1.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:"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,$(label_addY)f,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).0 $(y).0 $(z).0 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
# ---- 상시: interaction 클릭/타격 → playsound + 명령/투표 실행 ---- # ---- 상시: interaction 클릭/타격 → playsound + 명령/투표 실행 ----
# init main = 0 (퀴즈 시작 전 설정 단계) : 명령 직접 실행 # init main = 0 (퀴즈 시작 전 설정 단계) : 명령 직접 실행
# init main >= 10 (종료 상태) + 정지 버튼 : 명령 직접 실행 (재시작용, 투표 없음)
# 그 외 : trigger 투표 경로 # 그 외 : trigger 투표 경로
# 한 버튼에 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).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 if data storage mq:tmp btn{n:"test"} run $(c) $execute as @e[type=minecraft:interaction,tag=mq,tag=$(n)] 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 unless data storage mq:tmp btn{n:"test"} 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).0 $(y).0 $(z).0 unless score init main matches 0 run trigger $(n)
# 종료 상태(init 10/11)에서 정지 버튼 단일 클릭 → 즉시 commands/stop 으로 재시작 대기(init 0).
# 투표 집계는 repeat/triggers/trigger 가 init==5 에서만 하므로, 이 분기 없이는 종료 안내문
# ("종료를 눌러주세요")대로 눌러도 종료가 안 된다. commands/stop 이 init 을 0 으로 만들어
# 아래 투표 줄(unless init==0)은 같은 tick 에 자동으로 건너뛰어진다(중복 실행 없음).
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target if data storage mq:tmp btn{n:"stop"} as @s positioned $(x).0 $(y).0 $(z).0 if score init main matches 10.. run $(c)
$execute as @e[distance=0..,tag=mq,tag=$(n),type=minecraft:interaction] on target unless data storage mq:tmp btn{n:"test"} as @s positioned $(x).0 $(y).0 $(z).0 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,7 +6,6 @@
# 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축 동일하게 사용됨)
# label_addY : 기본 "-0.1"
# #
# 구현: defaults 컴파운드를 먼저 만들고 entry (mq:tmp.btn) 를 그 위에 merge # 구현: defaults 컴파운드를 먼저 만들고 entry (mq:tmp.btn) 를 그 위에 merge
# 한 뒤 다시 mq:tmp.btn 으로 되돌린다. data modify ... merge from 은 source # 한 뒤 다시 mq:tmp.btn 으로 되돌린다. data modify ... merge from 은 source
@@ -17,6 +16,6 @@
# 방식이었으나 MC 26.1.2 parser 가 해당 라인을 거부했음. merge 방식은 문제 # 방식이었으나 MC 26.1.2 parser 가 해당 라인을 거부했음. merge 방식은 문제
# 난 execute-unless-data 구문 자체를 제거.) # 난 execute-unless-data 구문 자체를 제거.)
data modify storage mq:tmp btn_default set value {label:"",label_color:"black",label_font:"minecraft:default",label_scale:"1.0",label_addY:"-0.1"} 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_default merge from storage mq:tmp btn
data modify storage mq:tmp btn set from storage mq:tmp btn_default data modify storage mq:tmp btn set from storage mq:tmp btn_default

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 with storage mq:main audio 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 with storage mq:main audio 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,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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 60 as @a at @s run playsound minecraft:ui.button.click $(source) @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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 120 as @a at @s run playsound minecraft:ui.button.click $(source) @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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 180 as @a at @s run playsound minecraft:ui.button.click $(source) @s ~ ~ ~ 1 1
execute if score timer main matches 200.. run scoreboard players set init main 11

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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 20 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 40 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @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 $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @s ~ ~ ~ 1 1
$execute if score timer main matches 60 as @a at @s run playsound minecraft:block.note_block.iron_xylophone $(source) @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,13 +0,0 @@
# 정답 공개 동안(init=6) 곡의 video URL 을 actionbar 에 흰색으로 계속 표시.
# 매 tick 항상 재전송한다. video 가 "" 이거나 없는 곡은 빈 문자열이 그대로 출력돼
# 아무것도 안 보이고, 매 tick 빈 actionbar 로 덮어써 잔상도 남지 않는다.
title @a actionbar [{"storage":"mq:main","nbt":"answer.video","color":"white"}]
execute if score timer main matches 300 run title @a title {"text":""}
# 다음 상태로 넘어가기 전 actionbar 비우기 — 안 비우면 마지막 URL 이 다음 곡(init=5)
# 초반까지 ~3초 잔상으로 남는다. select 보다 먼저 실행되어 깔끔히 지워진다.
execute if score timer main matches 300 run title @a actionbar {"text":""}
# warn-off execute-group
execute if score timer main matches 290 run function mq:images/clear
execute if score timer main matches 290 run function mq:videos/clear
execute if score timer main matches 300.. run function mq:quiz/select with storage mq:main

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

@@ -2,13 +2,9 @@ function mq:repeat/players
function mq:repeat/buttons/handler function mq:repeat/buttons/handler
function mq:repeat/triggers/handler function mq:repeat/triggers/handler
# 영상 모드 명령 큐를 매 tick 한 개씩 command_block 으로 실행
function mq:videos/drain
execute if score init main matches 2.. run function mq:repeat/timer 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,8 +0,0 @@
# 라운드 진행 시 preload 윈도우의 맨 앞(index+preload-1) 한 개를 캐시에 추가.
# 시작 preload 와 합쳐 항상 preload 개 버퍼가 굴러간다.
execute store result score cache_lo func.temp run data get storage mq:main video.preload
execute if score cache_lo func.temp matches ..0 run return 0
scoreboard players operation cache_lo func.temp += index main
scoreboard players remove cache_lo func.temp 1
scoreboard players operation cache_hi func.temp = cache_lo func.temp
function mq:videos/cache/fill

View File

@@ -1,6 +0,0 @@
# 추적 목록을 비우고 전체 캐시 wipe 를 큐에 적재 (command_block 으로 실행).
data modify storage mq:main video.cached set value []
data modify storage mq:tmp video set from storage mq:main video
execute store result storage mq:tmp video.num int 1 run scoreboard players get index main
data modify storage mq:tmp video.cmd set value "cache_clear"
function mq:videos/macro/cmd with storage mq:tmp video

View File

@@ -1,12 +0,0 @@
# 추적 캐시 갯수가 keep 을 넘으면 가장 먼저 받은 것부터(FIFO) 제거 큐에 적재.
# keep<=0 이면 무제한으로 보고 축출하지 않는다.
execute store result score cache_keep func.temp run data get storage mq:main video.keep
execute if score cache_keep func.temp matches ..0 run return 0
execute store result score cache_len func.temp run data get storage mq:main video.cached
execute if score cache_len func.temp <= cache_keep func.temp run return 0
data modify storage mq:tmp video set from storage mq:main video
data modify storage mq:tmp video.num set from storage mq:main video.cached[0].i
data modify storage mq:tmp video.cmd set value "cache_remove"
function mq:videos/macro/cmd with storage mq:tmp video
data remove storage mq:main video.cached[0]
function mq:videos/cache/evict

View File

@@ -1,11 +0,0 @@
# cache_lo..cache_hi 범위의 영상을 캐시 큐에 등록한다 (max_index 상한).
# 등록 후 keep 초과분은 FIFO 로 축출. 실제 videoCache add/remove 는 큐를 통해
# command_block 으로 실행되므로 모드 없이도 로드된다.
execute if score cache_lo func.temp > cache_hi func.temp run return run function mq:videos/cache/evict
execute if score cache_lo func.temp > max_index main run return run function mq:videos/cache/evict
data modify storage mq:tmp video set from storage mq:main video
execute store result storage mq:tmp video.num int 1 run scoreboard players get cache_lo func.temp
data modify storage mq:tmp video.cmd set value "cache_add"
function mq:videos/macro/cmd with storage mq:tmp video
scoreboard players add cache_lo func.temp 1
function mq:videos/cache/fill

View File

@@ -1,6 +0,0 @@
# 게임 시작 시 1번부터 preload 번까지 캐시를 미리 등록한다.
execute store result score cache_lo func.temp run data get storage mq:main video.preload
execute if score cache_lo func.temp matches ..0 run return 0
scoreboard players operation cache_hi func.temp = cache_lo func.temp
scoreboard players set cache_lo func.temp 1
function mq:videos/cache/fill

View File

@@ -1,4 +0,0 @@
data modify storage mq:tmp video set from storage mq:main video
execute store result storage mq:tmp video.num int 1 run scoreboard players get index main
data modify storage mq:tmp video.cmd set value "delete"
function mq:videos/macro/cmd with storage mq:tmp video

View File

@@ -1,18 +0,0 @@
# warn-off-file execute-group
# 매 tick: 큐(mq:main video.cmdq)에 대기 중인 모드 명령을 한 개씩 command_block 으로
# 실행한다. command_block 은 auto:1b 면 매 tick 재실행되므로(=videoCache add 중복
# 실패 스팸) 명령을 실행한 다음 tick 에 auto:0b 로 꺼서 1회만 실행되게 한다.
# 한 좌표 블록은 한 tick 에 1회만 켜지므로 큐를 1개씩 소진해 preload·축출 다건도
# 순서대로 처리된다.
data remove storage mq:tmp fire
execute if data storage mq:main video.cmdq[0] run data modify storage mq:tmp fire set from storage mq:main video
execute if data storage mq:main video.cmdq[0] run data modify storage mq:tmp fire.c set from storage mq:main video.cmdq[0].c
# 큐에 명령이 있으면 적재·실행하고 armed 표시 후 pop.
execute if data storage mq:tmp fire.c run function mq:videos/macro/drain with storage mq:tmp fire
execute if data storage mq:tmp fire.c run data modify storage mq:main video.armed set value 1b
execute if data storage mq:tmp fire.c run data remove storage mq:main video.cmdq[0]
# 큐가 비었고 직전에 켠 블록이 있으면 한 번만 꺼서 재실행을 막는다.
execute unless data storage mq:tmp fire.c if data storage mq:main {video:{armed:1b}} run function mq:videos/macro/drain_off with storage mq:main video
execute unless data storage mq:tmp fire.c run data modify storage mq:main video.armed set value 0b

View File

@@ -1,16 +0,0 @@
# 모드 명령을 "큐(mq:main video.cmdq)"에 문자열로 적재한다. 실제 실행은 매 tick
# videos/drain 이 command_block 으로 하나씩 처리한다. 데이터팩 로드 시 모드 명령을
# 직접 파싱하지 않으므로 영상 모드가 없어도 게임이 동작한다.
# delete : videoDelete
# play : videoPlace (캐시 추적중이면 video_N, 아니면 전체 URL)
# cache_add : videoCache add (이미 추적중이면 skip, 아니면 추적목록 append)
# cache_remove : videoCache remove
# cache_clear : videoCache clear
$execute if data storage mq:tmp {video:{cmd:"delete"}} run data modify storage mq:main video.cmdq append value {c:"videoDelete $(x) $(y) $(z)"}
$execute if data storage mq:tmp {video:{cmd:"play"}} if data storage mq:main video.cached[{i:$(num)}] run data modify storage mq:main video.cmdq append value {c:"videoPlace $(x) $(y) $(z) $(facing) $(w) $(h) $(sound) video_$(num)"}
$execute if data storage mq:tmp {video:{cmd:"play"}} unless data storage mq:main video.cached[{i:$(num)}] run data modify storage mq:main video.cmdq append value {c:"videoPlace $(x) $(y) $(z) $(facing) $(w) $(h) $(sound) $(namespace)/$(num)"}
$execute if data storage mq:tmp {video:{cmd:"cache_add"}} if data storage mq:main video.cached[{i:$(num)}] run return 0
$execute if data storage mq:tmp {video:{cmd:"cache_add"}} run data modify storage mq:main video.cmdq append value {c:"videoCache add video_$(num) $(namespace)/$(num)"}
$execute if data storage mq:tmp {video:{cmd:"cache_add"}} run data modify storage mq:main video.cached append value {i:$(num)}
$execute if data storage mq:tmp {video:{cmd:"cache_remove"}} run data modify storage mq:main video.cmdq append value {c:"videoCache remove video_$(num)"}
execute if data storage mq:tmp {video:{cmd:"cache_clear"}} run data modify storage mq:main video.cmdq append value {c:"videoCache clear"}

View File

@@ -1,3 +0,0 @@
# 큐에서 꺼낸 명령 1개를 공용 command_block 에 적재 후 auto 0→1 로 실행시킨다.
$setblock $(cmd_x) $(cmd_y) $(cmd_z) minecraft:command_block[conditional=false,facing=up]{Command:"$(c)",auto:0b}
$data modify block $(cmd_x) $(cmd_y) $(cmd_z) auto set value 1b

View File

@@ -1,2 +0,0 @@
# 직전 tick 에 켠 명령 블록을 끈다 (armed 일 때만 호출되므로 블록이 존재함).
$data modify block $(cmd_x) $(cmd_y) $(cmd_z) auto set value 0b

View File

@@ -1,9 +0,0 @@
# namespace 끝에 / 가 붙어 있으면 제거 (config 오타 방어).
# string 소스 마지막 글자만 떼서 비교 후, 맞으면 마지막 글자를 잘라낸다.
data modify storage mq:tmp ns_last set string storage mq:main video.namespace -1
execute if data storage mq:tmp {ns_last:"/"} run data modify storage mq:main video.namespace set string storage mq:main video.namespace 0 -1
# 캐시 추적 목록({i:N}), 모드 명령 큐, command_block armed 플래그 초기화.
data modify storage mq:main video.cached set value []
data modify storage mq:main video.cmdq set value []
data modify storage mq:main video.armed set value 0b

View File

@@ -1,6 +0,0 @@
# 정답 영상 재생을 큐에 적재. videoPlace 는 같은 앵커 좌표를 덮어쓰므로 별도
# clear 불필요. 캐시 추적중이면 video_N, 아니면 전체 URL 로 macro/cmd 가 결정.
data modify storage mq:tmp video set from storage mq:main video
execute store result storage mq:tmp video.num int 1 run scoreboard players get index main
data modify storage mq:tmp video.cmd set value "play"
function mq:videos/macro/cmd with storage mq:tmp video

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,101 +1,50 @@
# 부분 적용 가이드 (→ v1.0.26) # v1.0.20 → v1.0.21 부분 적용 가이드
전체 datapack zip 을 새로 풀지 않고, 이 폴더의 파일만 같은 경로에 전체 datapack 을 교체하지 않고, 이 폴더의 파일만 덮어쓰면 v1.0.21 과 동일한 상태가 됩니다.
덮어쓰면 v1.0.26 와 동일한 동작이 됩니다. 본인 월드 좌표·곡 목록 등이
들어 있는 `init/*.mcfunction` 은 일부러 포함하지 않았습니다 — 덮어쓰면
좌표가 날아가니까요.
## v1.0.26 = v1.0.25 + 문서 정리 ## 무엇이 바뀌었나
v1.0.26 자체에는 데이터팩 동작 변경이 없습니다. 변경 사항: ### 1. `repeat/buttons/btn.mcfunction` — 버튼이 같이 눌러지던 문제 + 라벨이 JSON 코드로 나오던 문제
- 삭제됐던 `docs/mc_video_player_mod_integration.md` 복구.
- 루트 `README.md` 의 오류 정정:
- 음원 채널 `weather` → 실제는 `player` (UI 비프만 `weather`) 로 정정.
- 폐기된 `marker` 스토리지/엔티티 설명을 “legacy 정리용” 으로 정리.
- `temp/` 부분 적용 패키지 v1.0.26 기준으로 복구 (이 폴더).
v1.0.25 의 데이터팩 코드를 이미 쓰고 있다면 mods · 데이터팩 변경 없이 **(a) 클릭 시 stone_button 도 같이 눌러짐**
끝납니다. **이 폴더를 적용해야 하는 건 v1.0.25 이전 버전에서 올라오는
경우** 입니다.
## 같이 들어 있는 누적 fix (v1.0.19 ~ v1.0.25) interaction 박스가 stone_button 의 hitbox 와 정확히 겹쳐 있어서, 한 번
클릭에 interaction 도 발화하고 stone_button 도 vanilla 클릭으로 인식해
`powered=true` 애니메이션이 같이 일어났습니다. interaction 박스를 버튼
면 바깥쪽으로 한 두께(0.125) 만큼 빼서, 플레이어 ray 가 stone_button 에
닿기 전에 interaction 에서 멈추도록 했습니다.
### 버튼 인프라 (`repeat/buttons/`) - south : 깊이 z 중심 0.0625 → -0.0625
- `btn.mcfunction` / `btn_prep.mcfunction` / `handler.mcfunction` - north : 깊이 z 중심 0.9375 → 1.0625
- v1.0.20: `positioned $(x).0 $(y).0 $(z).0` (정수 vec3 의 자동 +0.5 보정 - east : 깊이 x 중심 0.0625 → -0.0625
회피), `btn_prep``merge from` defaults 패턴 (`execute unless data - west : 깊이 x 중심 0.9375 → 1.0625
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`) **(b) 라벨이 `{"text":"게임시작",...}` JSON 텍스트 그대로 보임**
- 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/`) MC 1.20.5+ 부터 text_display 의 `text` 필드는 String 이 아니라 직접
- v1.0.25: 큰 `timer.mcfunction` 을 init 단계별 (`init2` 카운트다운, TextComponent compound 로 저장됩니다. 이전에는 String 안에 JSON 을 넣어서
`init6` 다음 곡, `init10` 엔딩) 서브함수로 분할. `timer.mcfunction` (`text:'{...}'`) 그 JSON 자체가 텍스트로 렌더링되었습니다. compound 형태
init 게이팅만 하고 각 서브를 호출. (`text:{text:"...",color:"...",font:"..."}`) 로 변경.
### 2. `repeat/buttons/btn_prep.mcfunction`
v1.0.20 에서 이미 수정됨 (defaults + merge 방식). 변경 없음 — 다만
v1.0.19 이하에서 점프하는 경우 같이 덮어써야 합니다.
## 적용 방법 ## 적용 방법
### 1. 모드 jar 확인 (이미 설치돼 있으면 skip) 서버의 datapack 폴더 (예: `world/datapacks/music_quiz/`) 기준으로 두 파일을
**반드시 덮어쓰세요**.
- `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 temp/data/mq/function/repeat/buttons/btn.mcfunction
-> <datapack>/data/mq/function/repeat/buttons/btn.mcfunction -> <datapack>/data/mq/function/repeat/buttons/btn.mcfunction (덮어쓰기)
temp/data/mq/function/repeat/buttons/btn_prep.mcfunction temp/data/mq/function/repeat/buttons/btn_prep.mcfunction
-> <datapack>/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 /reload
@@ -103,9 +52,8 @@ data modify storage mq:main button_defs append value {n:"start", x:..., y:..., z
## 확인 ## 확인
- 콘솔에 `btn_prep` / `btn` 파싱 에러 없음. - 버튼을 클릭해도 stone_button 의 `powered=true` 눌림 애니메이션이
- 버튼 클릭 시 stone_button powered 애니메이션 발생하지 않음 발생하지 않아야 합니다 (interaction 만 발화).
(interaction 박스가 한 두께 바깥에서 ray 를 가로채므로). - 버튼 아래 라벨이 `게임시작` / `정지` / `소리 테스트` 등으로 정상 표시
- 라벨이 버튼 바로 아래 벽면에 굵게 표시. (JSON 텍스트 노출 없음).
- 채팅정답 / 영상재생 모드 미설치 시 `/start` 가 사유 메시지와 함께 차단. - 콘솔에 파싱 에러 없음.
- 모드 둘 다 정상 설치되어 있으면 `/start` 정상 진행.

Some files were not shown because too many files have changed in this diff Show More