Files
mc_datapack/music_quiz/data/mq/function/repeat/buttons/btn.mcfunction
Claude (owner) 0d33d22467 music_quiz: 버튼 라벨 위치 + interaction 깊이 부호 수정
- text_display Y `~-1` → `~-0.25`: entity Y 가 텍스트 윗변이라
  ~-1 이면 텍스트 본체가 바닥에 떨어져 표시됐음. ~-0.25 로 두면
  버튼 바로 아래 벽면 [Y-1, Y] 의 위쪽 절반에 라벨이 자리잡음.
- text 에 bold:true 기본 적용.
- interaction 깊이 부호 반전: v1.0.21 에서 "버튼 바깥 한 두께
  만큼 밀어 동시 발화 회피" 의도였는데 부호를 반대로 잡아 벽
  안쪽으로 들어가 있었음 (south z=-0.0625 등). 플레이어 쪽
  (south z=0.1875 등) 으로 수정.

이전엔 raycast tie 로 우연히 동작했지만 호스트/각도에 따라 벽이
먼저 차단되는 케이스 발생.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:51:14 +09:00

117 lines
11 KiB
MCFunction
Raw Blame History

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