File size: 9,665 Bytes
3961da8
0563217
 
 
 
 
 
 
 
 
 
 
 
 
3da3874
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3961da8
3da3874
3961da8
 
 
 
 
 
 
 
 
 
 
 
 
 
3da3874
3961da8
 
 
3da3874
 
3961da8
 
 
3da3874
 
3961da8
3da3874
0563217
3da3874
 
 
 
 
 
 
 
 
 
 
 
3961da8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3da3874
3961da8
 
 
 
3da3874
3961da8
 
 
3da3874
3961da8
 
 
3da3874
 
 
3961da8
 
 
 
 
 
 
 
 
 
3da3874
3961da8
 
 
 
 
 
 
 
 
 
 
 
 
3da3874
 
 
0563217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3da3874
 
 
 
 
 
 
 
 
3961da8
 
3da3874
 
 
 
 
0563217
3961da8
 
 
 
3da3874
 
3961da8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/* patch_scene.mjs — v17: sân trước cửa hang + cờ MU + áo MU + bảng điểm danh
   VẤN ĐỀ v16: cờ MU đặt z=66 nhưng sân ngoài (OutdoorGround) chỉ bắt đầu z~76 và
   walkR guard chặn z<55 → vùng z 55..78 là khoảng trống giữa cửa hang và sân
   ngoài: không đất nền, vật phẩm vô hình.
   FIX v17 (chạy trên SOURCE GỐC cave3d.js — bản chưa patch):
   (A) Forecourt — sân đất trước cửa hang (z 55..80) nối cửa với sân ngoài.
   (B) Cờ MU/GVMU 2 hàng hai bên lối ra trên forecourt (z=62, z=74).
   (C) Ghế gỗ nghỉ chân cạnh forecourt.
   (D) Bảng điểm danh CHUYEN CAN THANG NAY dựng trái cửa (z=62).
   (E) Giá treo áo MU #7 dựng phải cửa (z=62).
   (F) Kéo dài OutdoorPath (11x86) nối liền forecourt.
   LƯU Ý: OutdoorScene trong SOURCE GỐC chỉ gồm SkyDome/Ground/Path/Props/Light
   (không có Flags/Bench — những cái đó chỉ tồn tại trong bản build v16).
   Nếu pattern lệch -> exit(1) để build fail. */
import fs from 'node:fs'

function patchFile(path, reps) {
  let s = fs.readFileSync(path, 'utf8')
  const failures = []
  for (const [oldStr, newStr] of reps) {
    const count = s.split(oldStr).length - 1
    if (count === 1) { s = s.replace(oldStr, newStr); continue }
    failures.push(`pattern not unique/found (count=${count}): ${oldStr.slice(0, 80)}`)
  }
  if (failures.length) {
    console.error(`PATCH SCENE FAILED (${path}):`)
    failures.forEach((f) => console.error(' -', f))
    process.exit(1)
  }
  fs.writeFileSync(path, s)
  console.log('patched ok:', path)
}

/* ================= cave3d.js ================= */
patchFile('frontend/cave3d.js', [
  /* ---- (A) Forecourt: san dat noi cua hang -> san ngoai ---- */
  [
    `function OutdoorGround() {`,
    `function Forecourt() {
  const geo = useMemo(() => {
    const m = new THREE.PlaneGeometry(64, 26, 40, 18)
    m.rotateX(-Math.PI / 2)
    const p = m.attributes.position
    const cols = new Float32Array(p.count * 3)
    const cA = new THREE.Color('#7d7258'), cB = new THREE.Color('#5f5540'), moss = new THREE.Color('#6d6a3e'), tmp = new THREE.Color()
    for (let i = 0; i < p.count; i++) {
      const x = p.getX(i), z = p.getZ(i)
      p.setY(i, fbm3(x * 0.09, z * 0.09, 0, 31) * 0.22 + 0.02)
      tmp.copy(cA).lerp(cB, fbm3(x * 0.16, z * 0.16, 3, 32) * 0.5 + 0.25)
      tmp.lerp(moss, Math.max(0, fbm3(x * 0.1, 9, z * 0.1, 33)) * 0.3)
      cols[i * 3] = tmp.r; cols[i * 3 + 1] = tmp.g; cols[i * 3 + 2] = tmp.b
    }
    m.setAttribute('color', new THREE.BufferAttribute(cols, 3))
    m.computeVertexNormals()
    return m
  }, [])
  return (
    <mesh geometry={geo} position={[0, 0, 67]} receiveShadow fog={false}>
      <meshLambertMaterial vertexColors fog={false} />
    </mesh>
  )
}
function OutdoorGround() {`,
  ],
  /* ---- (B2E) OutdoorScene mới: flags + bench + attendance + jersey ---- */
  [
    `function OutdoorScene() {
  return (
    <group>
      <SkyDome />
      <OutdoorGround />
      <OutdoorPath />
      <OutdoorProps />
      <OutdoorLight />
    </group>
  )
}`,
    `/* ---- Bang diem danh (chuyen can) truoc cua hang ---- */
function AttendanceForecourt() {
  const [rows, setRows] = React.useState(null)
  React.useEffect(() => {
    let alive = true
    fetch('/api/attendance').then(r => r.ok ? r.json() : Promise.reject()).then(d => {
      if (alive && d && d.rows) setRows(d.rows)
    }).catch(() => {
      if (!alive) return
      const r = []
      for (let i = 0; i < 5; i++) {
        const row = []
        for (let j = 0; j < 10; j++) row.push(Math.random() > 0.35 ? { name: '', number: 0 } : null)
        r.push(row)
      }
      setRows(r)
    })
    return () => { alive = false }
  }, [])
  const tex = React.useMemo(() => {
    const c = document.createElement('canvas'); c.width = 640; c.height = 320
    const g = c.getContext('2d')
    g.fillStyle = '#7a5f42'; g.fillRect(0, 0, 640, 320)
    g.strokeStyle = '#4a3522'; g.lineWidth = 4; g.strokeRect(4, 4, 632, 312)
    g.fillStyle = '#3a2a18'; g.font = '700 26px "Be Vietnam Pro", sans-serif'; g.textAlign = 'center'
    g.fillText('CHUYEN CAN THANG NAY', 320, 36)
    for (let r = 0; r < 5; r++) for (let c = 0; c < 10; c++) {
      const x = 16 + c * 61, y = 50 + r * 50
      g.fillStyle = '#5a442c'; g.fillRect(x, y, 56, 42)
      if (rows && rows[r] && rows[r][c]) {
        g.fillStyle = '#c8102e'; g.fillRect(x + 3, y + 3, 50, 36)
        g.fillStyle = '#fff'; g.font = '600 18px "Be Vietnam Pro", sans-serif'
        g.fillText('v', x + 28, y + 27)
      } else { g.fillStyle = '#8a6f4e'; g.fillRect(x + 3, y + 3, 50, 36) }
    }
    const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace
    return t
  }, [rows])
  if (!rows) return null
  return (
    <group position={[-4.2, 0, 62]} rotation={[0, 0.85, 0]} fog={false}>
      <mesh position={[0, 1.2, -0.12]}>
        <boxGeometry args={[2.5, 2.6, 0.2]} />
        <meshLambertMaterial color="#3d2a1a" flatShading fog={false} />
      </mesh>
      <mesh position={[0, -0.9, -0.1]}>
        <boxGeometry args={[0.18, 1.9, 0.18]} />
        <meshLambertMaterial color="#5a4328" flatShading fog={false} />
      </mesh>
      <mesh position={[0, 1.35, 0.05]}>
        <planeGeometry args={[2.2, 2.3]} />
        <meshBasicMaterial map={tex} transparent toneMapped={false} fog={false} />
      </mesh>
    </group>
  )
}
/* ---- Gia treo ao MU (jersey #7) canh cua ---- */
function JerseyStand() {
  const [tex, setTex] = React.useState(null)
  React.useEffect(() => {
    let alive = true
    const t = _jerseyTex()
    if (alive && t) setTex(t)
    return () => { alive = false }
  }, [])
  return (
    <group position={[5.4, 0, 62]} rotation={[0, -0.7, 0]} fog={false}>
      <mesh position={[0, 1.3, 0]}>
        <cylinderGeometry args={[0.07, 0.09, 2.6, 6]} />
        <meshLambertMaterial color="#4a3220" flatShading fog={false} />
      </mesh>
      <mesh position={[0, 2.62, 0]}>
        <sphereGeometry args={[0.1, 8, 8]} />
        <meshStandardMaterial color="#c9a24e" fog={false} />
      </mesh>
      <mesh position={[0, 2.05, 0]} rotation-x={0.12}>
        <planeGeometry args={[1.15, 1.45]} />
        <meshBasicMaterial map={tex ?? null} transparent side={THREE.DoubleSide} toneMapped={false} fog={false} />
      </mesh>
    </group>
  )
}
/* ---- Co phuon MU hai ben loi ra (forecourt) ---- */
function makePennantTex(text) {
  if (typeof document === 'undefined') return null
  const c = document.createElement('canvas'); c.width = 128; c.height = 128
  const g = c.getContext('2d')
  g.fillStyle = '#c8102e'; g.fillRect(0, 0, 128, 128)
  g.strokeStyle = '#ffd76a'; g.lineWidth = 6; g.strokeRect(4, 4, 120, 120)
  g.fillStyle = '#ffffff'; g.font = '700 40px "Be Vietnam Pro", sans-serif'
  g.textAlign = 'center'; g.textBaseline = 'middle'
  g.fillText(text, 64, 66)
  const t = new THREE.CanvasTexture(c); t.colorSpace = THREE.SRGBColorSpace
  return t
}
function OutdoorFlags() {
  const texM = useMemo(() => makePennantTex('MU'), [])
  const texG = useMemo(() => makePennantTex('GGMU'), [])
  const flags = [
    { x: -7, z: 62, tex: texM }, { x: 7, z: 62, tex: texG },
    { x: -11, z: 74, tex: texG }, { x: 11, z: 74, tex: texM },
  ]
  return (
    <group>
      {flags.map((f, i) => (
        <group key={i} position={[f.x, 0, f.z]} fog={false}>
          <mesh position={[0, 1.9, 0]} fog={false}>
            <cylinderGeometry args={[0.09, 0.12, 3.8, 6]} />
            <meshLambertMaterial color="#5a4328" flatShading fog={false} />
          </mesh>
          <mesh position={[0, 3.85, 0]} fog={false}>
            <sphereGeometry args={[0.12, 8, 8]} />
            <meshLambertMaterial color="#c9a24e" fog={false} />
          </mesh>
          <mesh position={[0.5, 3.0, 0]} fog={false}>
            <planeGeometry args={[1.4, 1.1]} />
            <meshBasicMaterial map={f.tex} transparent side={THREE.DoubleSide} fog={false} toneMapped={false} />
          </mesh>
        </group>
      ))}
    </group>
  )
}
/* ---- Ghe go nghi chan ---- */
function OutdoorBench() {
  const bench = (x, z, rot) => (
    <group key={`${x}-${z}`} position={[x, 0, z]} rotation-y={rot} fog={false}>
      <mesh position={[0, 0.5, 0]} fog={false}>
        <boxGeometry args={[1.9, 0.16, 0.55]} />
        <meshLambertMaterial color="#6a4a2c" flatShading fog={false} />
      </mesh>
      <mesh position={[0, 0.72, 0]} fog={false}>
        <boxGeometry args={[1.9, 0.12, 0.5]} />
        <meshLambertMaterial color="#7a5a38" flatShading fog={false} />
      </mesh>
      <mesh position={[-0.72, 0.22, 0]} fog={false}>
        <boxGeometry args={[0.12, 0.44, 0.5]} />
        <meshLambertMaterial color="#4a3220" flatShading fog={false} />
      </mesh>
      <mesh position={[0.72, 0.22, 0]} fog={false}>
        <boxGeometry args={[0.12, 0.44, 0.5]} />
        <meshLambertMaterial color="#4a3220" flatShading fog={false} />
      </mesh>
    </group>
  )
  return (
    <group>
      {bench(-6, 60, 0.12)}
      {bench(6, 60, -0.1)}
    </group>
  )
}
function OutdoorScene() {
  return (
    <group>
      <SkyDome />
      <OutdoorGround />
      <OutdoorPath />
      <OutdoorProps />
      <OutdoorFlags />
      <OutdoorBench />
      <AttendanceForecourt />
      <JerseyStand />
      <OutdoorLight />
    </group>
  )
}`,
  ],
  /* ---- (F) Keo dai OutdoorPath len forecourt nhin lien mach ---- */
  [
    `      <planeGeometry args={[11, 66]} />`,
    `      <planeGeometry args={[11, 86]} />`,
  ],
])

console.log('patch_scene.mjs OK — forecourt + MU flags + jersey stand + attendance board added')