Sinketji commited on
Commit
72315c4
·
verified ·
1 Parent(s): e14d98e

Create Index.html

Browse files
Files changed (1) hide show
  1. Index.html +659 -0
Index.html ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Blox City RP - Ultimate Edition</title>
7
+ <style>
8
+ body { margin: 0; overflow: hidden; background: #87CEEB; font-family: 'Verdana', sans-serif; user-select: none; -webkit-user-select: none; }
9
+ #game-ui { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
10
+
11
+ /* HUD */
12
+ #hud-top { position: absolute; top: 10px; left: 10px; right: 10px; display: flex; justify-content: space-between; pointer-events: auto; }
13
+ .stat-box { background: rgba(0, 0, 0, 0.7); color: white; padding: 10px 15px; border-radius: 12px; font-weight: bold; border: 2px solid #fff; box-shadow: 0 4px 6px rgba(0,0,0,0.5); display: flex; align-items: center; gap: 8px; font-size: 14px; }
14
+
15
+ /* ALERTS */
16
+ #message-area { position: absolute; top: 15%; width: 100%; text-align: center; color: #fff; font-size: 24px; text-shadow: 2px 2px 0 #000; font-weight: 900; pointer-events: none; opacity: 0; transition: opacity 0.5s; z-index: 20; }
17
+
18
+ /* CONTROLS */
19
+ .controls-area { position: absolute; bottom: 20px; pointer-events: auto; display: flex; gap: 15px; }
20
+ #controls-left { left: 20px; flex-direction: column; align-items: center; }
21
+ #controls-right { right: 20px; align-items: flex-end; flex-direction: column-reverse; }
22
+
23
+ .btn { background: rgba(255, 255, 255, 0.25); backdrop-filter: blur(5px); border: 2px solid rgba(255,255,255,0.6); border-radius: 50%; color: white; display: flex; justify-content: center; align-items: center; font-size: 20px; touch-action: manipulation; box-shadow: 0 4px 10px rgba(0,0,0,0.4); transition: transform 0.1s; cursor: pointer; }
24
+ .btn:active { transform: scale(0.9); background: rgba(255, 255, 255, 0.5); }
25
+
26
+ .dpad-row { display: flex; gap: 8px; }
27
+ .dpad-btn { width: 55px; height: 55px; }
28
+
29
+ .action-btn { width: 70px; height: 70px; margin-bottom: 15px; background: rgba(255, 193, 7, 0.8); border-color: #FFD54F; font-weight: bold; font-size: 12px; flex-direction: column; text-align: center; line-height: 1.1; color: #000; }
30
+ .jump-btn { width: 75px; height: 75px; background: rgba(33, 150, 243, 0.6); border-color: #64B5F6; }
31
+ .summon-btn { width: 50px; height: 50px; background: rgba(156, 39, 176, 0.6); border-color: #BA68C8; font-size: 20px; margin-bottom: 10px; }
32
+
33
+ /* COOKING UI */
34
+ #cooking-container { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 200px; background: #222; padding: 15px; border-radius: 10px; border: 3px solid #fff; pointer-events: none; text-align: center; color: white; z-index: 50; }
35
+ #cooking-bar-bg { width: 100%; height: 15px; background: #444; margin-top: 5px; border-radius: 10px; overflow: hidden; }
36
+ #cooking-bar-fill { width: 0%; height: 100%; background: #00E676; }
37
+
38
+ /* VEHICLE MENU */
39
+ #vehicle-menu { display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 350px; max-height: 60vh; background: #fff; border-radius: 15px; box-shadow: 0 20px 50px rgba(0,0,0,0.8); overflow-y: auto; pointer-events: auto; padding: 15px; z-index: 100; font-family: sans-serif; }
40
+ #vehicle-menu h3 { margin: 0 0 10px 0; text-align: center; color: #333; border-bottom: 2px solid #eee; padding-bottom: 5px; }
41
+ .v-item { display: flex; align-items: center; justify-content: space-between; padding: 10px; border-bottom: 1px solid #f0f0f0; }
42
+ .v-btn { background: #2196F3; color: white; border: none; padding: 6px 12px; border-radius: 15px; font-weight: bold; cursor: pointer; font-size: 12px; }
43
+ #close-menu { display: block; width: 100%; padding: 10px; margin-top: 15px; background: #f44336; color: white; border: none; border-radius: 8px; font-weight: bold; }
44
+ </style>
45
+ </head>
46
+ <body>
47
+
48
+ <!-- UI LAYER -->
49
+ <div id="game-ui">
50
+ <div id="hud-top">
51
+ <div class="stat-box">💵 $<span id="money-display">500</span></div>
52
+ <div class="stat-box" style="flex:1; justify-content: center; margin: 0 10px;">
53
+ <span id="job-display">🏠 Explore City</span>
54
+ </div>
55
+ </div>
56
+
57
+ <div id="message-area">Welcome to Blox City!</div>
58
+
59
+ <div id="cooking-container">
60
+ COOKING...
61
+ <div id="cooking-bar-bg"><div id="cooking-bar-fill"></div></div>
62
+ </div>
63
+
64
+ <div class="controls-area" id="controls-left">
65
+ <div class="dpad-row"><div class="btn dpad-btn" id="btn-up">⬆️</div></div>
66
+ <div class="dpad-row">
67
+ <div class="btn dpad-btn" id="btn-left">⬅️</div>
68
+ <div class="btn dpad-btn" id="btn-down">⬇️</div>
69
+ <div class="btn dpad-btn" id="btn-right">➡️</div>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="controls-area" id="controls-right">
74
+ <div class="btn summon-btn" id="btn-summon">🚗</div>
75
+ <div class="btn action-btn" id="context-btn">Action</div>
76
+ <div class="btn jump-btn" id="btn-jump">JUMP</div>
77
+ </div>
78
+ </div>
79
+
80
+ <!-- MENUS -->
81
+ <div id="vehicle-menu">
82
+ <h3>Garage (All Free!)</h3>
83
+ <div id="vehicle-list"></div>
84
+ <button id="close-menu">Close</button>
85
+ </div>
86
+
87
+ <!-- 3D ENGINE -->
88
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
89
+ <script>
90
+ /** --- CONFIG --- */
91
+ const STATE = {
92
+ money: 500,
93
+ isCooking: false,
94
+ holdingFood: false,
95
+ currentOrder: null,
96
+ inVehicle: null,
97
+ canJump: true,
98
+ velocity: new THREE.Vector3(),
99
+ keys: { w: false, a: false, s: false, d: false },
100
+ ownedVehicles: ['Scooter', 'Sedan', 'SportBike', 'SuperCar', 'Truck'], // All unlocked
101
+ };
102
+
103
+ const VEHICLES_DB = [
104
+ { name: 'Scooter', speed: 0.8, color: 0xFF5722, type: 'bike' },
105
+ { name: 'SportBike', speed: 1.5, color: 0x00E676, type: 'bike' },
106
+ { name: 'Sedan', speed: 1.2, color: 0x2196F3, type: 'car' },
107
+ { name: 'Truck', speed: 1.0, color: 0x5D4037, type: 'car' },
108
+ { name: 'SuperCar', speed: 2.2, color: 0xD50000, type: 'car' },
109
+ { name: 'Police', speed: 1.8, color: 0x111111, type: 'car' }
110
+ ];
111
+
112
+ /** --- SCENE SETUP --- */
113
+ const scene = new THREE.Scene();
114
+ scene.background = new THREE.Color(0x87CEEB);
115
+ scene.fog = new THREE.Fog(0x87CEEB, 20, 120);
116
+
117
+ const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
118
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
119
+ renderer.setSize(window.innerWidth, window.innerHeight);
120
+ renderer.shadowMap.enabled = true;
121
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
122
+ document.body.appendChild(renderer.domElement);
123
+
124
+ // Lights
125
+ const ambLight = new THREE.AmbientLight(0xffffff, 0.6);
126
+ scene.add(ambLight);
127
+ const dirLight = new THREE.DirectionalLight(0xffffff, 0.7);
128
+ dirLight.position.set(50, 100, 50);
129
+ dirLight.castShadow = true;
130
+ dirLight.shadow.mapSize.width = 2048;
131
+ dirLight.shadow.mapSize.height = 2048;
132
+ dirLight.shadow.camera.left = -60; dirLight.shadow.camera.right = 60;
133
+ dirLight.shadow.camera.top = 60; dirLight.shadow.camera.bottom = -60;
134
+ scene.add(dirLight);
135
+
136
+ /** --- TEXTURE GENERATORS --- */
137
+ function createTexture(color, type='plain') {
138
+ const cvs = document.createElement('canvas');
139
+ cvs.width = 64; cvs.height = 64;
140
+ const ctx = cvs.getContext('2d');
141
+ ctx.fillStyle = color;
142
+ ctx.fillRect(0,0,64,64);
143
+
144
+ if(type === 'brick') {
145
+ ctx.fillStyle = 'rgba(0,0,0,0.1)';
146
+ ctx.fillRect(0, 0, 64, 2); ctx.fillRect(0, 32, 64, 2);
147
+ ctx.fillRect(32, 0, 2, 32); ctx.fillRect(0, 32, 2, 32);
148
+ }
149
+ if(type === 'checkered') {
150
+ ctx.fillStyle = '#ffffff';
151
+ ctx.fillRect(0,0,32,32); ctx.fillRect(32,32,32,32);
152
+ ctx.fillStyle = '#000000';
153
+ ctx.fillRect(32,0,32,32); ctx.fillRect(0,32,32,32);
154
+ }
155
+ if(type === 'face') {
156
+ ctx.fillStyle = '#FFCC80'; ctx.fillRect(0,0,64,64);
157
+ ctx.fillStyle = 'black';
158
+ ctx.fillRect(15, 20, 8, 8); // Eye L
159
+ ctx.fillRect(41, 20, 8, 8); // Eye R
160
+ ctx.fillRect(20, 45, 24, 4); // Mouth
161
+ }
162
+
163
+ const tex = new THREE.CanvasTexture(cvs);
164
+ tex.magFilter = THREE.NearestFilter;
165
+ return tex;
166
+ }
167
+
168
+ const MATS = {
169
+ grass: new THREE.MeshLambertMaterial({ color: 0x4CAF50 }),
170
+ road: new THREE.MeshLambertMaterial({ color: 0x333333 }),
171
+ sidewalk: new THREE.MeshLambertMaterial({ color: 0x9E9E9E }),
172
+ skin: new THREE.MeshLambertMaterial({ color: 0xFFCC80 }),
173
+ face: new THREE.MeshLambertMaterial({ map: createTexture(null, 'face') }),
174
+ brickRed: new THREE.MeshLambertMaterial({ map: createTexture('#E57373', 'brick') }),
175
+ brickBlue: new THREE.MeshLambertMaterial({ map: createTexture('#64B5F6', 'brick') }),
176
+ floorCheck: new THREE.MeshLambertMaterial({ map: createTexture(null, 'checkered') }),
177
+ glass: new THREE.MeshPhongMaterial({ color: 0x81D4FA, transparent: true, opacity: 0.6, shininess: 100 }),
178
+ wood: new THREE.MeshLambertMaterial({ color: 0x8D6E63 }),
179
+ metal: new THREE.MeshStandardMaterial({ color: 0xAAAAAA, roughness: 0.2 })
180
+ };
181
+ MATS.floorCheck.map.repeat.set(4,4);
182
+ MATS.floorCheck.map.wrapS = THREE.RepeatWrapping;
183
+ MATS.floorCheck.map.wrapT = THREE.RepeatWrapping;
184
+
185
+ /** --- WORLD BUILDER --- */
186
+ // Ground
187
+ const ground = new THREE.Mesh(new THREE.PlaneGeometry(400, 400), MATS.grass);
188
+ ground.rotation.x = -Math.PI/2;
189
+ ground.receiveShadow = true;
190
+ scene.add(ground);
191
+
192
+ // Road Helper
193
+ function addRoad(x, z, w, l, rotated=false) {
194
+ const r = new THREE.Mesh(new THREE.PlaneGeometry(w, l), MATS.road);
195
+ r.rotation.x = -Math.PI/2;
196
+ if(rotated) r.rotation.z = Math.PI/2;
197
+ r.position.set(x, 0.02, z);
198
+ r.receiveShadow = true;
199
+ scene.add(r);
200
+
201
+ // Sidewalks
202
+ const sw1 = new THREE.Mesh(new THREE.PlaneGeometry(2, l), MATS.sidewalk);
203
+ sw1.rotation.x = -Math.PI/2;
204
+ if(rotated) sw1.rotation.z = Math.PI/2;
205
+ // Calculations simplified for demo
206
+ if(rotated) sw1.position.set(x, 0.03, z - w/2 - 1);
207
+ else sw1.position.set(x - w/2 - 1, 0.03, z);
208
+ scene.add(sw1);
209
+
210
+ const sw2 = sw1.clone();
211
+ if(rotated) sw2.position.set(x, 0.03, z + w/2 + 1);
212
+ else sw2.position.set(x + w/2 + 1, 0.03, z);
213
+ scene.add(sw2);
214
+ }
215
+
216
+ // Layout: Main Road (Z axis), Cross Road (X axis)
217
+ addRoad(0, 0, 14, 400);
218
+ addRoad(0, 0, 400, 14, true);
219
+
220
+ // --- PARK (Green Zone) ---
221
+ const parkGroup = new THREE.Group();
222
+ parkGroup.position.set(40, 0, 40);
223
+
224
+ const parkBase = new THREE.Mesh(new THREE.BoxGeometry(50, 0.5, 50), MATS.grass);
225
+ parkBase.position.y = 0.25;
226
+ parkGroup.add(parkBase);
227
+
228
+ // Trees
229
+ for(let i=0; i<8; i++) {
230
+ const tree = new THREE.Group();
231
+ const trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.7, 3), MATS.wood);
232
+ trunk.position.y = 1.5;
233
+ const leaves = new THREE.Mesh(new THREE.ConeGeometry(3, 6, 8), new THREE.MeshLambertMaterial({color:0x2E7D32}));
234
+ leaves.position.y = 5;
235
+ tree.add(trunk, leaves);
236
+ tree.position.set((Math.random()-0.5)*40, 0, (Math.random()-0.5)*40);
237
+ parkGroup.add(tree);
238
+ }
239
+ // Bench
240
+ const bench = new THREE.Group();
241
+ const seat = new THREE.Mesh(new THREE.BoxGeometry(4, 0.2, 1), MATS.wood);
242
+ seat.position.y = 1;
243
+ const leg1 = new THREE.Mesh(new THREE.BoxGeometry(0.2, 1, 1), MATS.metal);
244
+ leg1.position.set(-1.8, 0.5, 0);
245
+ const leg2 = leg1.clone(); leg2.position.set(1.8, 0.5, 0);
246
+ bench.add(seat, leg1, leg2);
247
+ bench.position.set(0, 0, 0);
248
+ parkGroup.add(bench);
249
+
250
+ scene.add(parkGroup);
251
+
252
+ // --- RESTAURANT (Detailed Interior) ---
253
+ const restGroup = new THREE.Group();
254
+ restGroup.position.set(-40, 0, 40);
255
+
256
+ // Floor
257
+ const floor = new THREE.Mesh(new THREE.BoxGeometry(30, 0.2, 20), MATS.floorCheck);
258
+ floor.position.y = 0.1;
259
+ restGroup.add(floor);
260
+
261
+ // Walls (leaving gaps)
262
+ const wallMat = new THREE.MeshLambertMaterial({color: 0xFFEB3B});
263
+ const wallBack = new THREE.Mesh(new THREE.BoxGeometry(30, 8, 1), wallMat);
264
+ wallBack.position.set(0, 4, -10);
265
+ const wallLeft = new THREE.Mesh(new THREE.BoxGeometry(1, 8, 20), wallMat);
266
+ wallLeft.position.set(-15, 4, 0);
267
+ const wallRight = new THREE.Mesh(new THREE.BoxGeometry(1, 8, 20), wallMat);
268
+ wallRight.position.set(15, 4, 0);
269
+ const roof = new THREE.Mesh(new THREE.BoxGeometry(32, 1, 22), new THREE.MeshLambertMaterial({color: 0xFBC02D}));
270
+ roof.position.y = 8.5;
271
+
272
+ restGroup.add(wallBack, wallLeft, wallRight, roof);
273
+
274
+ // Kitchen
275
+ const counter = new THREE.Mesh(new THREE.BoxGeometry(15, 2.5, 2), new THREE.MeshLambertMaterial({color:0xffffff}));
276
+ counter.position.set(0, 1.25, -4);
277
+ restGroup.add(counter);
278
+
279
+ // Machines
280
+ const grill = new THREE.Mesh(new THREE.BoxGeometry(3, 2, 2), new THREE.MeshLambertMaterial({color:0x333333}));
281
+ grill.position.set(-3, 1.5, -9);
282
+ restGroup.add(grill); // Grill at back wall
283
+
284
+ const fryer = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), new THREE.MeshLambertMaterial({color:0xCCCCCC}));
285
+ fryer.position.set(2, 1.5, -9);
286
+ restGroup.add(fryer);
287
+
288
+ // Tables & Chairs
289
+ function addTable(x, z) {
290
+ const t = new THREE.Mesh(new THREE.CylinderGeometry(2, 2, 1.5), new THREE.MeshLambertMaterial({color:0xD32F2F}));
291
+ t.position.set(x, 0.75, z);
292
+ restGroup.add(t);
293
+ }
294
+ addTable(-8, 5); addTable(8, 5); addTable(-8, 0); addTable(8, 0);
295
+
296
+ // Sign
297
+ const sign = new THREE.Mesh(new THREE.BoxGeometry(20, 3, 1), new THREE.MeshBasicMaterial({color:0xD50000}));
298
+ sign.position.set(0, 10, 10);
299
+ restGroup.add(sign);
300
+
301
+ // NPC
302
+ function createNPC(x, z, color) {
303
+ const npc = new THREE.Group();
304
+ const nBody = new THREE.Mesh(new THREE.BoxGeometry(0.8, 1, 0.4), new THREE.MeshLambertMaterial({color: color}));
305
+ nBody.position.y = 1.5;
306
+ const nHead = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.5), MATS.skin);
307
+ nHead.position.y = 2.3;
308
+ npc.add(nBody, nHead);
309
+ npc.position.set(x, 0, z);
310
+ return npc;
311
+ }
312
+ restGroup.add(createNPC(0, -6, 0x4CAF50)); // Cashier/Customer
313
+ restGroup.add(createNPC(10, 5, 0x9C27B0)); // Customer eating
314
+
315
+ restGroup.userData = { type: 'restaurant' };
316
+ scene.add(restGroup);
317
+
318
+ // --- HOUSES ---
319
+ const buildings = [];
320
+ function buildDetailedHouse(x, z, color) {
321
+ const hGroup = new THREE.Group();
322
+ hGroup.position.set(x, 0, z);
323
+
324
+ // Main structure
325
+ const body = new THREE.Mesh(new THREE.BoxGeometry(10, 8, 10), new THREE.MeshLambertMaterial({map: MATS.brickRed.map, color: color}));
326
+ body.position.y = 4;
327
+ hGroup.add(body);
328
+
329
+ // Roof
330
+ const roof = new THREE.Mesh(new THREE.ConeGeometry(9, 4, 4), MATS.wood);
331
+ roof.position.y = 10;
332
+ roof.rotation.y = Math.PI/4;
333
+ hGroup.add(roof);
334
+
335
+ // Door
336
+ const door = new THREE.Mesh(new THREE.BoxGeometry(2.5, 4.5, 0.5), MATS.wood);
337
+ door.position.set(0, 2.25, 5.1);
338
+ hGroup.add(door);
339
+
340
+ // Door Knob
341
+ const knob = new THREE.Mesh(new THREE.SphereGeometry(0.15), new THREE.MeshBasicMaterial({color:0xFFD700}));
342
+ knob.position.set(0.8, 2.25, 5.4);
343
+ hGroup.add(knob);
344
+
345
+ // Windows
346
+ const winGeo = new THREE.BoxGeometry(2.5, 2.5, 0.5);
347
+ const w1 = new THREE.Mesh(winGeo, MATS.glass); w1.position.set(-2.5, 5, 5.1);
348
+ const w2 = new THREE.Mesh(winGeo, MATS.glass); w2.position.set(2.5, 5, 5.1);
349
+ hGroup.add(w1, w2);
350
+
351
+ // Fence
352
+ const fenceGeo = new THREE.BoxGeometry(0.2, 1.5, 0.2);
353
+ for(let i=-6; i<=6; i+=2) {
354
+ const p = new THREE.Mesh(fenceGeo, new THREE.MeshLambertMaterial({color:0xffffff}));
355
+ p.position.set(i, 0.75, 7);
356
+ hGroup.add(p);
357
+ }
358
+
359
+ hGroup.userData = { type: 'house', deliveryPoint: new THREE.Vector3(x, 1, z+8) };
360
+ buildings.push(hGroup);
361
+ scene.add(hGroup);
362
+ }
363
+
364
+ // Generate Neighborhood
365
+ for(let i=0; i<4; i++) {
366
+ buildDetailedHouse(-40, -40 - (i*25), 0xFFCDD2);
367
+ buildDetailedHouse(40, -40 - (i*25), 0xBBDEFB);
368
+ buildDetailedHouse(-40 - (i*25), 80, 0xFFF9C4);
369
+ }
370
+
371
+ /** --- PLAYER & VEHICLES --- */
372
+ const player = new THREE.Group();
373
+ // Head
374
+ const pHead = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.6, 0.6), MATS.face);
375
+ pHead.position.y = 2.7;
376
+ // Body
377
+ const pTorso = new THREE.Mesh(new THREE.BoxGeometry(0.9, 1.1, 0.45), new THREE.MeshLambertMaterial({color:0x1976D2}));
378
+ pTorso.position.y = 1.85;
379
+ // Arms
380
+ const pArmL = new THREE.Mesh(new THREE.BoxGeometry(0.35, 1.1, 0.35), MATS.skin); pArmL.position.set(-0.7, 1.85, 0);
381
+ const pArmR = new THREE.Mesh(new THREE.BoxGeometry(0.35, 1.1, 0.35), MATS.skin); pArmR.position.set(0.7, 1.85, 0);
382
+ // Legs
383
+ const pLegL = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.2, 0.4), new THREE.MeshLambertMaterial({color:0x212121})); pLegL.position.set(-0.25, 0.6, 0);
384
+ const pLegR = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.2, 0.4), new THREE.MeshLambertMaterial({color:0x212121})); pLegR.position.set(0.25, 0.6, 0);
385
+
386
+ // Food Held
387
+ const foodItem = new THREE.Group();
388
+ const burger = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.25, 0.15), new THREE.MeshLambertMaterial({color:0x795548}));
389
+ const bunTop = new THREE.Mesh(new THREE.SphereGeometry(0.26, 8, 8, 0, 6.3, 0, 1.5), new THREE.MeshLambertMaterial({color:0xFFA000}));
390
+ bunTop.position.y = 0.1;
391
+ foodItem.add(burger, bunTop);
392
+ foodItem.position.set(0, -0.5, 0.4);
393
+ foodItem.rotation.x = Math.PI/4;
394
+ foodItem.visible = false;
395
+ pArmR.add(foodItem);
396
+
397
+ player.add(pHead, pTorso, pArmL, pArmR, pLegL, pLegR);
398
+ player.position.set(0, 0, 20);
399
+ player.castShadow = true;
400
+ scene.add(player);
401
+
402
+ const worldVehicles = [];
403
+
404
+ function spawnVehicle(data, x, z) {
405
+ const vGroup = new THREE.Group();
406
+ vGroup.position.set(x, 0.5, z);
407
+
408
+ const matBody = new THREE.MeshPhongMaterial({color: data.color, shininess: 80});
409
+ const matDark = new THREE.MeshLambertMaterial({color: 0x222222});
410
+
411
+ if(data.type === 'car') {
412
+ // Detailed Car Body
413
+ const chassis = new THREE.Mesh(new THREE.BoxGeometry(2, 0.8, 4), matBody);
414
+ chassis.position.y = 0.6;
415
+ vGroup.add(chassis);
416
+
417
+ const cabin = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.7, 2.2), matBody);
418
+ cabin.position.set(0, 1.3, -0.3);
419
+ vGroup.add(cabin);
420
+
421
+ // Windows
422
+ const windshield = new THREE.Mesh(new THREE.PlaneGeometry(1.6, 0.6), MATS.glass);
423
+ windshield.position.set(0, 1.35, 0.81); windshield.rotation.x = -0.3;
424
+ vGroup.add(windshield);
425
+
426
+ // Headlights
427
+ const hl1 = new THREE.Mesh(new THREE.CircleGeometry(0.2, 16), new THREE.MeshBasicMaterial({color:0xFFFACD}));
428
+ hl1.position.set(-0.6, 0.6, 2.01); vGroup.add(hl1);
429
+ const hl2 = hl1.clone(); hl2.position.set(0.6, 0.6, 2.01); vGroup.add(hl2);
430
+
431
+ // Wheels (Detailed)
432
+ const wGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.3, 16); wGeo.rotateZ(Math.PI/2);
433
+ const pos = [[1, 0.4, 1.2], [-1, 0.4, 1.2], [1, 0.4, -1.2], [-1, 0.4, -1.2]];
434
+ pos.forEach(p => {
435
+ const w = new THREE.Mesh(wGeo, matDark); w.position.set(...p); vGroup.add(w);
436
+ // Rims
437
+ const rim = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.2, 0.31, 8), new THREE.MeshStandardMaterial({color:0xCCCCCC}));
438
+ rim.rotation.z = Math.PI/2; w.add(rim);
439
+ });
440
+
441
+ } else {
442
+ // Bike
443
+ const body = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.5, 1.5), matBody);
444
+ body.position.y = 0.6; vGroup.add(body);
445
+ // Handle
446
+ const handle = new THREE.Mesh(new THREE.BoxGeometry(1, 0.1, 0.1), matDark);
447
+ handle.position.set(0, 1.2, 0.5); vGroup.add(handle);
448
+ // Wheels
449
+ const wGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.1, 16); wGeo.rotateZ(Math.PI/2);
450
+ const w1 = new THREE.Mesh(wGeo, matDark); w1.position.set(0, 0.4, 0.8); vGroup.add(w1);
451
+ const w2 = new THREE.Mesh(wGeo, matDark); w2.position.set(0, 0.4, -0.8); vGroup.add(w2);
452
+ }
453
+
454
+ vGroup.userData = { isVehicle: true, info: data };
455
+ worldVehicles.push(vGroup);
456
+ scene.add(vGroup);
457
+ return vGroup;
458
+ }
459
+
460
+ // Spawn Initial Vehicle
461
+ spawnVehicle(VEHICLES_DB[2], 5, 15);
462
+
463
+ /** --- INPUTS & LOGIC --- */
464
+ const keys = STATE.keys;
465
+ document.addEventListener('keydown', e => {
466
+ const k = e.key.toLowerCase();
467
+ if(keys[k] !== undefined) keys[k] = true;
468
+ if(e.code === 'Space') jump();
469
+ if(k === 'e') action();
470
+ });
471
+ document.addEventListener('keyup', e => {
472
+ const k = e.key.toLowerCase();
473
+ if(keys[k] !== undefined) keys[k] = false;
474
+ });
475
+
476
+ // Touch
477
+ const bindBtn = (id, key) => {
478
+ const el = document.getElementById(id);
479
+ el.addEventListener('touchstart', (e)=>{e.preventDefault(); keys[key]=true});
480
+ el.addEventListener('touchend', (e)=>{e.preventDefault(); keys[key]=false});
481
+ };
482
+ bindBtn('btn-up','w'); bindBtn('btn-down','s'); bindBtn('btn-left','a'); bindBtn('btn-right','d');
483
+ document.getElementById('btn-jump').addEventListener('touchstart', (e)=>{e.preventDefault(); jump()});
484
+ document.getElementById('context-btn').addEventListener('touchstart', (e)=>{e.preventDefault(); action()});
485
+ document.getElementById('context-btn').addEventListener('click', action);
486
+
487
+ function jump() {
488
+ if(!STATE.inVehicle && STATE.canJump) {
489
+ STATE.velocity.y = 0.5; STATE.canJump = false;
490
+ }
491
+ }
492
+
493
+ function showMsg(txt) {
494
+ const el = document.getElementById('message-area');
495
+ el.innerText = txt; el.style.opacity=1;
496
+ setTimeout(()=>el.style.opacity=0, 2500);
497
+ }
498
+
499
+ function action() {
500
+ // Exit Vehicle
501
+ if(STATE.inVehicle) {
502
+ const v = STATE.inVehicle;
503
+ player.position.copy(v.position).add(new THREE.Vector3(3, 0, 0));
504
+ player.visible = true;
505
+ STATE.inVehicle = null;
506
+ return;
507
+ }
508
+
509
+ // Enter Vehicle
510
+ if(!STATE.inVehicle) {
511
+ let closest = null, minD = 5;
512
+ worldVehicles.forEach(v => {
513
+ if(player.position.distanceTo(v.position) < minD) { minD = player.position.distanceTo(v.position); closest = v; }
514
+ });
515
+ if(closest) {
516
+ STATE.inVehicle = closest;
517
+ player.visible = false;
518
+ showMsg("Driving " + closest.userData.info.name);
519
+ return;
520
+ }
521
+ }
522
+
523
+ // Cooking
524
+ if(player.position.distanceTo(restGroup.position) < 20 && !STATE.holdingFood && !STATE.isCooking) {
525
+ STATE.isCooking = true;
526
+ document.getElementById('cooking-container').style.display = 'block';
527
+ let prog = 0;
528
+ const iv = setInterval(()=>{
529
+ prog += 4;
530
+ document.getElementById('cooking-bar-fill').style.width = prog + '%';
531
+ if(prog>=100) {
532
+ clearInterval(iv);
533
+ STATE.isCooking = false;
534
+ STATE.holdingFood = true;
535
+ foodItem.visible = true;
536
+ document.getElementById('cooking-container').style.display = 'none';
537
+ // Mission
538
+ const h = buildings[Math.floor(Math.random()*buildings.length)];
539
+ STATE.currentOrder = { target: h, reward: 50 };
540
+ document.getElementById('job-display').innerText = "🏠 Deliver Food!";
541
+ showMsg("Order Ready! Find the House!");
542
+ // Marker
543
+ if(window.marker) scene.remove(window.marker);
544
+ window.marker = new THREE.Mesh(new THREE.ConeGeometry(1,2,4), new THREE.MeshBasicMaterial({color:0xFFFF00}));
545
+ window.marker.rotation.x = Math.PI;
546
+ window.marker.position.copy(h.position).add(new THREE.Vector3(0, 12, 0));
547
+ scene.add(window.marker);
548
+ }
549
+ }, 100);
550
+ return;
551
+ }
552
+
553
+ // Deliver
554
+ if(STATE.holdingFood && STATE.currentOrder) {
555
+ if(player.position.distanceTo(STATE.currentOrder.target.userData.deliveryPoint) < 6) {
556
+ STATE.money += STATE.currentOrder.reward;
557
+ document.getElementById('money-display').innerText = STATE.money;
558
+ STATE.holdingFood = false;
559
+ foodItem.visible = false;
560
+ STATE.currentOrder = null;
561
+ document.getElementById('job-display').innerText = "🍔 Return to Restaurant";
562
+ showMsg("Delivered! +$50");
563
+ if(window.marker) { scene.remove(window.marker); window.marker=null; }
564
+ return;
565
+ }
566
+ }
567
+ }
568
+
569
+ // --- SHOP / SUMMON UI ---
570
+ const menu = document.getElementById('vehicle-menu');
571
+ document.getElementById('btn-summon').onclick = () => {
572
+ menu.style.display = 'block';
573
+ const list = document.getElementById('vehicle-list');
574
+ list.innerHTML = '';
575
+ VEHICLES_DB.forEach(v => {
576
+ const div = document.createElement('div');
577
+ div.className = 'v-item';
578
+ div.innerHTML = `<b>${v.name}</b> <button class="v-btn">RIDE</button>`;
579
+ div.querySelector('button').onclick = () => {
580
+ spawnVehicle(v, player.position.x+2, player.position.z);
581
+ menu.style.display = 'none';
582
+ };
583
+ list.appendChild(div);
584
+ });
585
+ };
586
+ document.getElementById('close-menu').onclick = () => menu.style.display='none';
587
+
588
+ /** --- GAME LOOP --- */
589
+ function animate() {
590
+ requestAnimationFrame(animate);
591
+
592
+ // Context Button Text
593
+ let txt = "Action";
594
+ if(STATE.inVehicle) txt = "EXIT";
595
+ else if(player.position.distanceTo(restGroup.position) < 20 && !STATE.holdingFood) txt = "COOK";
596
+ else if(STATE.currentOrder && player.position.distanceTo(STATE.currentOrder.target.userData.deliveryPoint)<6) txt = "DELIVER";
597
+ else {
598
+ worldVehicles.forEach(v => { if(player.position.distanceTo(v.position)<5) txt="DRIVE"; });
599
+ }
600
+ document.getElementById('context-btn').innerText = txt;
601
+
602
+ // Physics
603
+ if(STATE.inVehicle) {
604
+ const v = STATE.inVehicle;
605
+ const speed = v.userData.info.speed * 0.5;
606
+ if(keys.w) v.translateZ(speed);
607
+ if(keys.s) v.translateZ(-speed/2);
608
+ if(keys.a) v.rotation.y += 0.05;
609
+ if(keys.d) v.rotation.y -= 0.05;
610
+
611
+ // Camera
612
+ const off = new THREE.Vector3(0, 6, -10).applyAxisAngle(new THREE.Vector3(0,1,0), v.rotation.y);
613
+ camera.position.lerp(v.position.clone().add(off), 0.1);
614
+ camera.lookAt(v.position);
615
+ player.position.copy(v.position); // Sync hidden player
616
+ } else {
617
+ const speed = 0.3;
618
+ if(keys.w) player.translateZ(speed);
619
+ if(keys.s) player.translateZ(-speed);
620
+ if(keys.a) player.rotation.y += 0.08;
621
+ if(keys.d) player.rotation.y -= 0.08;
622
+
623
+ // Gravity
624
+ if(!STATE.canJump) {
625
+ STATE.velocity.y -= 0.03;
626
+ player.position.y += STATE.velocity.y;
627
+ if(player.position.y <= 0) {
628
+ player.position.y = 0; STATE.canJump = true; STATE.velocity.y = 0;
629
+ }
630
+ }
631
+
632
+ // Camera
633
+ const off = new THREE.Vector3(0, 7, -9).applyAxisAngle(new THREE.Vector3(0,1,0), player.rotation.y);
634
+ camera.position.lerp(player.position.clone().add(off), 0.1);
635
+ camera.lookAt(player.position.clone().add(new THREE.Vector3(0, 3, 0)));
636
+
637
+ // Anim
638
+ if(keys.w || keys.s) {
639
+ const t = Date.now() * 0.01;
640
+ pLegL.rotation.x = Math.sin(t)*0.5;
641
+ pLegR.rotation.x = -Math.sin(t)*0.5;
642
+ pArmL.rotation.x = -Math.sin(t)*0.5;
643
+ pArmR.rotation.x = Math.sin(t)*0.5;
644
+ }
645
+ }
646
+
647
+ if(window.marker) window.marker.rotation.y += 0.05;
648
+ renderer.render(scene, camera);
649
+ }
650
+ animate();
651
+
652
+ window.onresize = () => {
653
+ camera.aspect = window.innerWidth/window.innerHeight;
654
+ camera.updateProjectionMatrix();
655
+ renderer.setSize(window.innerWidth, window.innerHeight);
656
+ };
657
+ </script>
658
+ </body>
659
+ </html>