docs: README·연동문서를 영상 시스템/선택 모드 현행화

- README: 정답 영상 시스템(커맨드블럭 큐·preload/FIFO 캐시·동적 src),
  gif 페인팅, videos/ 파일트리, video 스토리지/스코어보드/설정, 영상 모드
  선택화 설치 절차, 2026-06-05 변경 이력 추가
- mc_video_player_mod_integration.md: 가드가 차단→경고만으로 바뀐 점 반영,
  README 에서 상호 링크

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
This commit is contained in:
Claude (owner)
2026-06-05 18:28:28 +09:00
parent 248fa96562
commit 4129b345c1
2 changed files with 139 additions and 37 deletions

123
README.md
View File

@@ -25,18 +25,26 @@
### 외부 모드 의존성 (서버/클라) ### 외부 모드 의존성 (서버/클라)
서버 측 검증이 들어가 있어 다음 두 모드가 반드시 깔려 있어야 `/start` 모드는 **필수 1 + 선택 1** 구성이다.
진행된다. 미설치 시 `commands/start` 의 게이트가 사유와 함께 차단한다.
- **`mc_chat_answer_mod` v1.3.7+** — 서버 전용. 채팅으로 정답 입력을 - **`mc_chat_answer_mod` v1.3.7+ (필수)** — 서버 전용. 채팅으로 정답 입력을
받는다. presence 는 `#server mq_chat_mod` 점수로 매 tick 표시되며, 받는다. 정답 판정의 핵심이라 없으면 `/start` 가 차단된다. presence 는
`SERVER_STARTED` / `END_DATA_PACK_RELOAD` / `PlayerJoin` / `ServerTick` `#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 https://git.tkrmagid.kr/tkrmagid/mc_chat_answer_mod/releases/tag/v1.3.7
- **`mc_video_player_mod`** — 클라이언트 + 서버 모두 필요. 서버 컴포넌트가 - **`mc_video_player_mod` (선택)** — 정답 영상 재생용. **없어도 게임은
`#server mq_video_mod` 를 1 로 갱신하고, 클라 join handshake 가 도착하면 정상 진행되며 영상만 표시되지 않는다.** `/start` 는 차단하지 않고 노란
`<player> mq_video_mod` 를 1 로 set. `/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% 바닐라 — 의존 플러그인 없음 (모드 외) ### 100% 바닐라 — 의존 플러그인 없음 (모드 외)
@@ -51,10 +59,48 @@
비프와 종료 클릭 같은 UI 사운드는 별도로 `weather` 채널을 사용한다. 비프와 종료 클릭 같은 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` 값으로 관리한다.
@@ -155,7 +201,18 @@ music_quiz/
│ ├── 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 디스패치 분할 │ │ ├── timers/{init2,init6,init10}.mcfunction # init 단계별 timer 디스패치 분할
@@ -178,6 +235,10 @@ 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` — 플레이어 트리거
스토리지: 스토리지:
@@ -186,6 +247,11 @@ 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}`
— 정답 영상 설정 + 런타임 캐시 상태. `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` — 버튼·트리거 정의
@@ -203,7 +269,11 @@ music_quiz/
`/reload` 후 반영된다. `/reload` 후 반영된다.
- **`init/config.mcfunction`** — 주제, 스폰 위치, 오디오 설정(`audio`), - **`init/config.mcfunction`** — 주제, 스폰 위치, 오디오 설정(`audio`),
정답 페인팅 좌표(`image`). `title``[ … ]` 채팅 접두사로도 사용된다. 정답 페인팅 좌표(`image`), 정답 영상 설정(`video`). `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 매칭** 되므로 순서 변경 시 리소스팩도 함께 재생성해야
@@ -222,13 +292,16 @@ JSON 텍스트 컴포넌트가 storage 참조를 일관되게 지원하지 않
### 설치 ### 설치
1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사. 1. 서버 월드 폴더 `datapacks/``music_quiz/` 디렉터리째 복사.
2. 서버 mods 폴더에 `mc_chat_answer_mod` v1.3.7+ `mc_video_player_mod` 2. 서버 mods 폴더에 `mc_chat_answer_mod` v1.3.7+ (필수) 설치.
jar 설치. `mc_video_player_mod` 는 클라이언트 측에도 설치 필요. `mc_video_player_mod` (선택) 는 정답 영상을 쓸 때만 서버 + 클라이언트
양쪽에 설치한다. 없어도 게임은 정상 동작하며 영상만 표시되지 않는다.
3. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용 3. minecraft_launcher 에서 생성한 `musicquiz` 리소스팩을 클라이언트에 적용
(런처가 자동 처리). (런처가 자동 처리).
4. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상. 4. 서버 `/reload` — 리로드 성공 메시지가 채팅에 표시되면 정상.
5. `mq:init/buttons` 에 정의된 좌표 부근에 6개 버튼이 자동 배치된다. 5. `mq:init/buttons` 에 정의된 좌표 부근에 6개 버튼이 자동 배치된다.
6. `start` 버튼을 눌러 게임 시작 — 모드 미설치 시 사유와 함께 차단된다. 6. `start` 버튼을 눌러 게임 시작 — `mc_chat_answer_mod` (필수) 미설치 시
사유와 함께 차단된다. `mc_video_player_mod` (선택) 미설치 시에는 노란
경고만 출력하고 영상 없이 진행된다.
### 좌표 의존성 (주의) ### 좌표 의존성 (주의)
@@ -243,6 +316,28 @@ JSON 텍스트 컴포넌트가 storage 참조를 일관되게 지원하지 않
## 변경 이력 ## 변경 이력
### 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/라벨 미세조정 + 곡목록·좌표 갱신 ### 2026-05-19 — v1.0.25: 버튼 hitbox/라벨 미세조정 + 곡목록·좌표 갱신
- `repeat/buttons/btn.mcfunction`: interaction hitbox 미세조정 (`width` - `repeat/buttons/btn.mcfunction`: interaction hitbox 미세조정 (`width`

View File

@@ -10,12 +10,16 @@
| `<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 이름으로 의미를
구분한다. 데이터팩 `mq:commands/start` 가드는 두 단계로 검사: 구분한다. **영상재생 모드는 선택 모드라 미설치여도 `/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 무의미).
> 본 모드는 클라이언트가 직접 영상 렌더링하므로 per-player handshake 도 필요함. > 그쪽은 정답 판정의 핵심이라 **없으면 `/start` 가 실제로 차단된다.** 본
> 모드는 클라이언트가 직접 영상 렌더링하므로 per-player handshake 가 필요하고,
> 그 점수는 차단이 아니라 **플레이어별 경고**에만 쓰인다.
## 데이터팩 측이 이미 제공하는 것 ## 데이터팩 측이 이미 제공하는 것
@@ -32,25 +36,24 @@ 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 as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 모드 적용 후 다시 입장해주세요.","color":"red"}] execute unless score #server mq_video_mod matches 1 run function mq:tellraw {"text":"영상재생 모드가 서버에 미설치 — 영상 없이 진행됩니다.","color":"yellow","msg":""}
# 한 명이라도 누락이면 시작 차단 # 서버는 있는데 본인 클라만 부재 — 본인에게만 경고
execute if entity @a[scores={mq_video_mod=..0}] run return run function mq:tellraw {"text":"필수 모드 미설치 플레이어가 있어 시작할 수 없습니다.","color":"red","msg":""} execute if score #server mq_video_mod matches 1 as @a[scores={mq_video_mod=..0}] run tellraw @s ["",{"text":"영상재생 모드 미설치 — 본인 화면에는 영상이 표시되지 않습니다.","color":"yellow"}]
``` ```
`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 이어도 게임은 진행되며, 경고
출력과 영상 표시 여부에만 영향을 준다.)
## 모드가 구현해야 하는 동작 ## 모드가 구현해야 하는 동작
@@ -135,12 +138,13 @@ 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 단위 → 주기 재전송이 살아 있는 한 start - payload 송신 → 서버 처리는 ms 단위 → 주기 재전송이 살아 있는 한 경고
타이밍 race 없음. 오탐 race 없음.
### 대안: `/trigger` 방식 (별도 서버 컴포넌트 불필요) ### 대안: `/trigger` 방식 (별도 서버 컴포넌트 불필요)
@@ -162,18 +166,21 @@ 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 점수 확인.