Offex commited on
Commit
4c188ba
·
verified ·
1 Parent(s): 99a7a3a

Rename index.html to app.py

Browse files
Files changed (2) hide show
  1. app.py +40 -0
  2. index.html +0 -1458
app.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from datetime import datetime
3
+ from zoneinfo import ZoneInfo
4
+
5
+ def get_times():
6
+
7
+ zones = {
8
+ "🗽 New York": "America/New_York",
9
+ "🤠 Texas": "America/Chicago",
10
+ "🏔 Denver": "America/Denver",
11
+ "🌴 California": "America/Los_Angeles",
12
+ "❄ Alaska": "America/Anchorage",
13
+ "🌺 Hawaii": "Pacific/Honolulu"
14
+ }
15
+
16
+ text = "🇺🇸 USA LIVE CLOCK\n\n"
17
+
18
+ for city, zone in zones.items():
19
+ now = datetime.now(ZoneInfo(zone))
20
+
21
+ text += (
22
+ f"{city}\n"
23
+ f"⏰ Time : {now.strftime('%I:%M:%S %p')}\n"
24
+ f"📅 Date : {now.strftime('%d-%m-%Y')}\n"
25
+ f"📆 Day : {now.strftime('%A')}\n\n"
26
+ )
27
+
28
+ return text
29
+
30
+ with gr.Blocks() as demo:
31
+ gr.Markdown("# 🇺🇸 USA Digital Clock")
32
+ output = gr.Textbox(lines=30, label="Live Time")
33
+
34
+ demo.load(
35
+ get_times,
36
+ outputs=output,
37
+ every=1
38
+ )
39
+
40
+ demo.launch()
index.html DELETED
@@ -1,1458 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="hi">
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, viewport-fit=cover">
6
- <title>Free Fire - Landscape Professional Edition</title>
7
- <!-- Tailwind CSS for high-quality HUD styling -->
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <!-- Three.js for 3D engine -->
10
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
11
- <style>
12
- body, html {
13
- margin: 0;
14
- padding: 0;
15
- width: 100%;
16
- height: 100%;
17
- overflow: hidden;
18
- user-select: none;
19
- -webkit-user-select: none;
20
- background-color: #0c0f12;
21
- font-family: 'Impact', 'Arial Black', sans-serif;
22
- }
23
- #gameCanvas {
24
- width: 100%;
25
- height: 100%;
26
- display: block;
27
- }
28
- /* Rotate Phone Hint Overlay */
29
- #rotatePrompt {
30
- display: none;
31
- }
32
- @media (orientation: portrait) {
33
- #rotatePrompt {
34
- display: flex;
35
- }
36
- }
37
- /* Custom styled Fire Button for mobile console feel */
38
- .fire-btn-ring {
39
- box-shadow: 0 0 20px rgba(239, 68, 68, 0.6);
40
- background: radial-gradient(circle, rgba(239,68,68,0.95) 0%, rgba(185,28,28,0.95) 100%);
41
- }
42
- .jump-btn-ring {
43
- box-shadow: 0 0 15px rgba(255, 255, 255, 0.2);
44
- background: rgba(255, 255, 255, 0.15);
45
- }
46
- /* Custom scrollbars / overlays hidden */
47
- * {
48
- outline: none;
49
- -webkit-tap-highlight-color: transparent;
50
- }
51
- </style>
52
- </head>
53
- <body>
54
-
55
- <!-- Landscape Rotation Prompt (Shows only in Portrait Mode) -->
56
- <div id="rotatePrompt" class="fixed inset-0 bg-[#0c0f12] z-[100] flex flex-col justify-center items-center text-center p-6 text-white">
57
- <div class="animate-bounce mb-6">
58
- <svg class="w-20 h-20 text-yellow-400" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
59
- <path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" />
60
- </svg>
61
- </div>
62
- <h2 class="text-2xl font-black text-yellow-400 uppercase tracking-widest">BEHTAR EXPERIENCE KE LIYE</h2>
63
- <p class="text-sm text-gray-400 mt-2 font-sans">Apne phone ko rotate karein aur Auto-Rotate on karein! 📱🔄</p>
64
- </div>
65
-
66
- <!-- Main Game Canvas -->
67
- <canvas id="gameCanvas"></canvas>
68
-
69
- <!-- Crosshair in the Center -->
70
- <div id="crosshair" class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none z-20 hidden">
71
- <div class="relative w-8 h-8 flex items-center justify-center">
72
- <div class="absolute w-1 h-1 bg-red-500 rounded-full"></div>
73
- <div class="absolute w-4 h-[2px] bg-green-400 left-0"></div>
74
- <div class="absolute w-4 h-[2px] bg-green-400 right-0"></div>
75
- <div class="absolute w-[2px] h-4 bg-green-400 top-0"></div>
76
- <div class="absolute w-[2px] h-4 bg-green-400 bottom-0"></div>
77
- </div>
78
- </div>
79
-
80
- <!-- UI Overlay (Full Console Layout) -->
81
- <div id="uiOverlay" class="absolute inset-0 pointer-events-none flex flex-col justify-between p-3 text-white">
82
-
83
- <!-- TOP HUD BAR -->
84
- <div class="flex justify-between items-start w-full">
85
- <!-- Left Side: Mini Stats & Sound state -->
86
- <div class="flex gap-2 items-center pointer-events-auto">
87
- <div class="bg-black/70 backdrop-blur-md px-3 py-1.5 rounded-lg border border-yellow-500/20 flex items-center gap-3">
88
- <div class="text-center border-r border-gray-700 pr-2.5">
89
- <span class="text-[9px] text-gray-400 block font-sans">ALIVE</span>
90
- <span id="aliveCounter" class="text-lg font-extrabold text-yellow-400">100</span>
91
- </div>
92
- <div class="text-center pr-1">
93
- <span class="text-[9px] text-gray-400 block font-sans">KILLS</span>
94
- <span id="killCounter" class="text-lg font-extrabold text-red-500">0</span>
95
- </div>
96
- </div>
97
- <!-- Audio Status Indicator -->
98
- <button id="audioInitBtn" class="bg-black/70 hover:bg-black/90 p-2 rounded-lg border border-white/10 flex items-center justify-center">
99
- 🔊 <span class="text-[10px] font-sans ml-1 text-gray-300">Tap Sound</span>
100
- </button>
101
- </div>
102
-
103
- <!-- Center: Kill Notifications Feed -->
104
- <div class="flex flex-col items-center gap-1.5 max-w-xs mt-1" id="killFeed">
105
- <!-- Live combat logs added here dynamically -->
106
- </div>
107
-
108
- <!-- Right Side: Mini Map and Next Safe Zone Circle tracker -->
109
- <div class="flex gap-2.5 items-start pointer-events-auto">
110
- <div class="flex flex-col items-end gap-1">
111
- <span id="zoneTimerText" class="bg-black/80 text-yellow-400 px-2 py-0.5 rounded text-[10px] font-sans tracking-tight border border-yellow-500/30">Next Zone: 45s</span>
112
- <span id="dangerAlert" class="bg-red-600 px-2 py-0.5 rounded text-[9px] text-white animate-pulse hidden">ZONE SHRINKING!</span>
113
- </div>
114
- <div class="relative w-20 h-20 bg-black/80 rounded-xl border border-yellow-500 overflow-hidden shadow-lg shadow-black/80">
115
- <canvas id="minimapCanvas" class="w-full h-full" width="80" height="80"></canvas>
116
- </div>
117
- </div>
118
- </div>
119
-
120
- <!-- CENTER ACTIONS SCREEN (Plane / Skydiving / Game alerts) -->
121
- <div class="absolute top-1/3 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-2 text-center">
122
- <h1 id="screenTitle" class="text-3xl font-black text-yellow-400 drop-shadow-[0_3px_5px_rgba(0,0,0,0.9)] tracking-widest">FREE FIRE</h1>
123
- <p id="screenSubtitle" class="text-[11px] font-sans text-gray-300 bg-black/60 px-4 py-1 rounded-full border border-white/5">Airbus island par pahunch rhi hai. Koodne ke liye button dabayein!</p>
124
-
125
- <button id="actionBtn" class="pointer-events-auto mt-3 px-6 py-2.5 bg-gradient-to-r from-yellow-500 to-orange-600 text-black font-extrabold text-sm rounded-lg shadow-md shadow-orange-500/20 active:scale-95 transition-all flex items-center gap-1 uppercase tracking-wider border border-white/20">
126
- 🚀 EJECT (PLANE SE COODIEN)
127
- </button>
128
- </div>
129
-
130
- <!-- BOTTOM HUD (Compact Console HUD) -->
131
- <div class="w-full flex justify-between items-end gap-4">
132
-
133
- <!-- Bottom Left: Movement Area & HP bar inside card -->
134
- <div class="w-1/3 max-w-[200px]">
135
- <!-- Static UI Joystick Guide on Left -->
136
- <div id="joystickGuide" class="text-white/40 text-[9px] font-sans uppercase tracking-widest mb-1 select-none">
137
- ← Drag left side to Walk
138
- </div>
139
- <!-- Mini Player card -->
140
- <div class="bg-black/75 backdrop-blur-md p-2 rounded-lg border border-white/10 pointer-events-auto">
141
- <div class="flex justify-between items-center text-[10px] font-sans mb-1">
142
- <span class="text-green-400 font-bold">● YOUR HEALTH</span>
143
- <span id="hpValue" class="font-extrabold">200 / 200 HP</span>
144
- </div>
145
- <!-- Pro Progress Bar -->
146
- <div class="w-full h-2.5 bg-gray-900 rounded-full overflow-hidden border border-gray-700/50 p-[1.5px]">
147
- <div id="hpBar" class="h-full bg-gradient-to-r from-emerald-500 to-green-400 rounded-full transition-all duration-100" style="width: 100%"></div>
148
- </div>
149
- </div>
150
- </div>
151
-
152
- <!-- Bottom Center: Active Looted Weapon Panel -->
153
- <div class="w-1/3 max-w-[220px] pointer-events-auto">
154
- <div id="weaponSlot" class="bg-black/80 backdrop-blur-md border border-yellow-500/30 px-3 py-2 rounded-xl flex items-center justify-between shadow-lg">
155
- <div class="text-left">
156
- <span id="gunTypeLabel" class="text-[8px] bg-yellow-500/20 text-yellow-400 px-1.5 py-0.5 rounded uppercase font-sans font-bold">MELEE</span>
157
- <h3 id="gunName" class="text-sm font-black text-white mt-1">Fist (Khaali Hath)</h3>
158
- </div>
159
- <div class="text-right border-l border-white/10 pl-3">
160
- <span id="gunAmmo" class="text-xs font-mono text-yellow-400 block font-bold">-- / --</span>
161
- <span class="text-[8px] text-gray-400 font-sans block">AMMO</span>
162
- </div>
163
- </div>
164
- </div>
165
-
166
- <!-- Bottom Right: Fire & Jump Mobile Actions -->
167
- <div class="w-1/3 flex justify-end gap-3 pointer-events-auto">
168
- <!-- Jump button -->
169
- <button id="mobileJumpBtn" class="jump-btn-ring w-12 h-12 bg-white/10 hover:bg-white/25 active:bg-white/30 border border-white/20 text-white rounded-full flex items-center justify-center font-bold text-[10px] shadow-md select-none touch-none">
170
- JUMP
171
- </button>
172
- <!-- Fire action Button (Bigger and glowing) -->
173
- <button id="mobileFireBtn" class="fire-btn-ring w-16 h-16 text-white rounded-full flex flex-col items-center justify-center select-none touch-none transform active:scale-90 transition-transform">
174
- <span class="text-xl">🔥</span>
175
- <span class="text-[8px] font-sans font-bold text-white/90">FIRE</span>
176
- </button>
177
- </div>
178
-
179
- </div>
180
- </div>
181
-
182
- <!-- Invisible Touch-Look Camera Swipe Pad on Right Half -->
183
- <div id="lookTouchPad" class="fixed top-0 right-0 w-1/2 h-full z-10 pointer-events-auto select-none touch-none"></div>
184
-
185
- <!-- Dynamic Joystick Knob (Left Side) -->
186
- <div id="joystickArea" class="fixed bottom-6 left-8 w-24 h-24 bg-white/10 border border-white/20 rounded-full z-20 pointer-events-auto flex items-center justify-center select-none touch-none">
187
- <div id="joystickKnob" class="w-10 h-10 bg-white/90 rounded-full shadow-lg pointer-events-none transition-transform duration-75"></div>
188
- </div>
189
-
190
- <!-- GAME OVER / VICTORY MODAL -->
191
- <div id="gameOverScreen" class="fixed inset-0 bg-black/95 backdrop-blur-md flex flex-col justify-center items-center text-center p-6 text-white hidden z-50">
192
- <h1 id="gameOverTitle" class="text-5xl font-black text-yellow-500 uppercase tracking-widest mb-2">BOOYAH!</h1>
193
- <p id="gameOverSub" class="text-sm font-sans text-gray-300 mb-6">Aapne island par sabhi 99 players ko dher kiya!</p>
194
-
195
- <div class="bg-white/5 border border-white/10 rounded-xl p-4 mb-6 w-full max-w-xs flex justify-around">
196
- <div>
197
- <span class="text-[10px] text-gray-400 font-sans block">YOUR KILLS</span>
198
- <span id="goKills" class="text-2xl font-black text-yellow-400">0</span>
199
- </div>
200
- <div class="border-r border-white/10 my-1"></div>
201
- <div>
202
- <span class="text-[10px] text-gray-400 font-sans block">SURVIVAL RANK</span>
203
- <span id="goRank" class="text-2xl font-black text-orange-400">#1 / 100</span>
204
- </div>
205
- </div>
206
-
207
- <button onclick="window.location.reload()" class="px-6 py-3 bg-yellow-500 hover:bg-yellow-400 text-black font-black rounded-lg text-sm uppercase tracking-wider shadow-lg active:scale-95 transition-all">
208
- Restart Match 🔄
209
- </button>
210
- </div>
211
-
212
- <script>
213
- // --- SYNTHESIZED SOUND ENGINE (Web Audio API) ---
214
- let audioCtx = null;
215
-
216
- function initAudio() {
217
- if (audioCtx) return;
218
- audioCtx = new (window.AudioContext || window.webkitAudioContext)();
219
- document.getElementById('audioInitBtn').innerHTML = "🔊 <span class='text-[10px] font-sans ml-1 text-green-400'>Sound Active</span>";
220
- playBGM(); // Start synth BGM loop on first click
221
- }
222
-
223
- document.getElementById('audioInitBtn').addEventListener('click', () => {
224
- initAudio();
225
- });
226
-
227
- // Synthesize Gunfire Sound (White noise + sweep down)
228
- function playShootSound() {
229
- if (!audioCtx) return;
230
- try {
231
- // Buffer Noise source
232
- const bufferSize = audioCtx.sampleRate * 0.15; // short burst
233
- const buffer = audioCtx.createBuffer(1, bufferSize, audioCtx.sampleRate);
234
- const data = buffer.getChannelData(0);
235
- for (let i = 0; i < bufferSize; i++) {
236
- data[i] = Math.random() * 2 - 1;
237
- }
238
- const noise = audioCtx.createBufferSource(buffer);
239
- const filter = audioCtx.createBiquadFilter();
240
- filter.type = 'bandpass';
241
- filter.frequency.value = 800;
242
-
243
- const gainNode = audioCtx.createGain();
244
- gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime);
245
- gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.15);
246
-
247
- noise.connect(filter);
248
- filter.connect(gainNode);
249
- gainNode.connect(audioCtx.destination);
250
- noise.start();
251
-
252
- // Base frequency boom
253
- const osc = audioCtx.createOscillator();
254
- const oscGain = audioCtx.createGain();
255
- osc.type = 'triangle';
256
- osc.frequency.setValueAtTime(150, audioCtx.currentTime);
257
- osc.frequency.exponentialRampToValueAtTime(40, audioCtx.currentTime + 0.12);
258
-
259
- oscGain.gain.setValueAtTime(0.4, audioCtx.currentTime);
260
- oscGain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.12);
261
-
262
- osc.connect(oscGain);
263
- oscGain.connect(audioCtx.destination);
264
- osc.start();
265
- osc.stop(audioCtx.currentTime + 0.15);
266
- } catch (e) {}
267
- }
268
-
269
- // Hit Indicator beep
270
- function playHitSound() {
271
- if (!audioCtx) return;
272
- try {
273
- const osc = audioCtx.createOscillator();
274
- const gain = audioCtx.createGain();
275
- osc.type = 'sine';
276
- osc.frequency.setValueAtTime(600, audioCtx.currentTime);
277
- gain.gain.setValueAtTime(0.2, audioCtx.currentTime);
278
- gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.08);
279
- osc.connect(gain);
280
- gain.connect(audioCtx.destination);
281
- osc.start();
282
- osc.stop(audioCtx.currentTime + 0.1);
283
- } catch (e) {}
284
- }
285
-
286
- // BGM Synth loop
287
- function playBGM() {
288
- if (!audioCtx) return;
289
- try {
290
- const notes = [110, 130, 146, 164, 110, 130, 196, 164];
291
- let index = 0;
292
-
293
- setInterval(() => {
294
- if (state.isGameOver) return;
295
- const osc = audioCtx.createOscillator();
296
- const gain = audioCtx.createGain();
297
- osc.type = 'sawtooth';
298
- osc.frequency.setValueAtTime(notes[index % notes.length], audioCtx.currentTime);
299
-
300
- gain.gain.setValueAtTime(0.04, audioCtx.currentTime); // quiet ambient volume
301
- gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.4);
302
-
303
- osc.connect(gain);
304
- gain.connect(audioCtx.destination);
305
- osc.start();
306
- osc.stop(audioCtx.currentTime + 0.5);
307
-
308
- index++;
309
- }, 400);
310
- } catch (e) {}
311
- }
312
-
313
-
314
- // --- WEAPONS METADATA ---
315
- const GUNS = [
316
- { id: 'fist', name: 'Fist (Khaali Hath)', type: 'Melee', damage: 15, range: 4, ammo: '∞', maxAmmo: '∞', color: 0x8c7853 },
317
- { id: 'm1887', name: 'M1887 (Double Shotgun)', type: 'Shotgun', damage: 85, range: 12, ammo: 2, maxAmmo: 2, reloadTime: 1.2, color: 0xcc2222 },
318
- { id: 'mp40', name: 'MP40 (Sub-machine)', type: 'SMG', damage: 19, range: 22, ammo: 30, maxAmmo: 30, reloadTime: 1.8, color: 0x00d2ff },
319
- { id: 'ak47', name: 'AK47 (High Damage)', type: 'AR', damage: 38, range: 42, ammo: 30, maxAmmo: 30, reloadTime: 2.2, color: 0xe67e22 },
320
- { id: 'awm', name: 'AWM (One-Tap Sniper)', type: 'Sniper', damage: 150, range: 90, ammo: 5, maxAmmo: 5, reloadTime: 3.0, color: 0x2ecc71 },
321
- { id: 'groza', name: 'Groza (Legendary)', type: 'AR', damage: 44, range: 46, ammo: 30, maxAmmo: 30, reloadTime: 2.0, color: 0x9b59b6 }
322
- ];
323
-
324
- const MAP_SIZE = 800; // optimized world size
325
-
326
- // --- GAME SYSTEM STATE ---
327
- const state = {
328
- phase: 'plane', // 'plane', 'skydiving', 'parachute', 'ground'
329
- playersCount: 100,
330
- kills: 0,
331
- health: 200,
332
- maxHealth: 200,
333
- isGameOver: false,
334
-
335
- zone: {
336
- x: 0,
337
- z: 0,
338
- radius: 350,
339
- targetRadius: 350,
340
- shrinkTimer: 45,
341
- isActive: false
342
- },
343
-
344
- // Keyboard binds
345
- keys: { w: false, a: false, s: false, d: false, space: false },
346
-
347
- // Smooth Joystick state
348
- joy: { active: false, dx: 0, dy: 0 },
349
-
350
- // Weapon Slots
351
- currentWeapon: GUNS[0],
352
- lastFired: 0,
353
- reloading: false
354
- };
355
-
356
- // --- THREE.JS INITIALIZATION ---
357
- const scene = new THREE.Scene();
358
- scene.background = new THREE.Color(0xb3d9ff); // Sky color
359
- scene.fog = new THREE.FogExp2(0xb3d9ff, 0.002);
360
-
361
- const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1500);
362
- const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('gameCanvas'), antialias: true, alpha: false });
363
- renderer.setSize(window.innerWidth, window.innerHeight);
364
- renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
365
- renderer.shadowMap.enabled = true;
366
- renderer.shadowMap.type = THREE.PCFSoftShadowMap;
367
-
368
- // --- LIGHTING ---
369
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.55);
370
- scene.add(ambientLight);
371
-
372
- const sunLight = new THREE.DirectionalLight(0xfff7e6, 1.3);
373
- sunLight.position.set(200, 300, 100);
374
- sunLight.castShadow = true;
375
- sunLight.shadow.mapSize.width = 1024;
376
- sunLight.shadow.mapSize.height = 1024;
377
- sunLight.shadow.camera.near = 0.5;
378
- sunLight.shadow.camera.far = 1000;
379
- const d = 300;
380
- sunLight.shadow.camera.left = -d;
381
- sunLight.shadow.camera.right = d;
382
- sunLight.shadow.camera.top = d;
383
- sunLight.shadow.camera.bottom = -d;
384
- scene.add(sunLight);
385
-
386
- // --- LANDSCAPE PROCEDURAL ISLAND HEIGHT GENERATION ---
387
- const terrainGeom = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 32, 32);
388
- terrainGeom.rotateX(-Math.PI / 2);
389
-
390
- const pos = terrainGeom.attributes.position;
391
- for (let i = 0; i < pos.count; i++) {
392
- const x = pos.getX(i);
393
- const z = pos.getZ(i);
394
- const dist = Math.sqrt(x*x + z*z);
395
- let height = 0;
396
-
397
- if (dist < MAP_SIZE / 2) {
398
- // Natural hills using math functions
399
- height = Math.sin(x * 0.02) * Math.cos(z * 0.02) * 10;
400
- height += Math.sin(x * 0.005) * Math.sin(z * 0.005) * 20;
401
-
402
- // Coastal smoothing falloff
403
- const edgeFactor = (MAP_SIZE / 2 - dist) / (MAP_SIZE / 2);
404
- height = height * Math.min(1, edgeFactor * 3.0);
405
- } else {
406
- height = -10;
407
- }
408
- pos.setY(i, height);
409
- }
410
- terrainGeom.computeVertexNormals();
411
-
412
- const terrainMat = new THREE.MeshStandardMaterial({
413
- color: 0x4f7c32, // vivid grass green
414
- roughness: 0.95,
415
- metalness: 0.02,
416
- flatShading: true
417
- });
418
- const terrain = new THREE.Mesh(terrainGeom, terrainMat);
419
- terrain.receiveShadow = true;
420
- scene.add(terrain);
421
-
422
- // Water level
423
- const waterGeom = new THREE.PlaneGeometry(MAP_SIZE * 2, MAP_SIZE * 2);
424
- const waterMat = new THREE.MeshStandardMaterial({
425
- color: 0x235d9f,
426
- roughness: 0.1,
427
- metalness: 0.85,
428
- transparent: true,
429
- opacity: 0.75
430
- });
431
- const water = new THREE.Mesh(waterGeom, waterMat);
432
- water.rotateX(-Math.PI / 2);
433
- water.position.y = -2;
434
- scene.add(water);
435
-
436
- function getTerrainHeight(x, z) {
437
- const dist = Math.sqrt(x*x + z*z);
438
- if (dist >= MAP_SIZE / 2) return -2;
439
- let h = Math.sin(x * 0.02) * Math.cos(z * 0.02) * 10;
440
- h += Math.sin(x * 0.005) * Math.sin(z * 0.005) * 20;
441
- const edgeFactor = (MAP_SIZE / 2 - dist) / (MAP_SIZE / 2);
442
- return h * Math.min(1, edgeFactor * 3.0);
443
- }
444
-
445
- // --- DECORATIVE OBSTACLES (TREES & RED COLOURED HOUSES) ---
446
- const structures = [];
447
-
448
- // Spawn Houses
449
- const hWallMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee });
450
- const hRoofMat = new THREE.MeshStandardMaterial({ color: 0xcc2222 }); // Bright red roof
451
-
452
- function buildHouse(x, z) {
453
- const h = getTerrainHeight(x, z);
454
- if (h < 0) return;
455
-
456
- const houseGroup = new THREE.Group();
457
-
458
- const wallMesh = new THREE.Mesh(new THREE.BoxGeometry(10, 7, 10), hWallMat);
459
- wallMesh.position.y = 3.5;
460
- wallMesh.castShadow = true;
461
- wallMesh.receiveShadow = true;
462
- houseGroup.add(wallMesh);
463
-
464
- const roofMesh = new THREE.Mesh(new THREE.ConeGeometry(8.5, 4.5, 4), hRoofMat);
465
- roofMesh.rotateY(Math.PI / 4);
466
- roofMesh.position.y = 7.5 + 1.25;
467
- roofMesh.castShadow = true;
468
- houseGroup.add(roofMesh);
469
-
470
- houseGroup.position.set(x, h, z);
471
- scene.add(houseGroup);
472
-
473
- structures.push({ x: x, z: z, radius: 6.5 });
474
- }
475
-
476
- // Place 22 houses randomly on island
477
- for (let i = 0; i < 22; i++) {
478
- const angle = Math.random() * Math.PI * 2;
479
- const d = 40 + Math.random() * 250;
480
- buildHouse(Math.cos(angle)*d, Math.sin(angle)*d);
481
- }
482
-
483
- // --- WEAPONS/GUN PICKUPS ON GROUND ---
484
- const weaponDrops = [];
485
-
486
- function spawnWeaponPickup(gun, x, z) {
487
- const h = getTerrainHeight(x, z) + 0.8;
488
- const wGroup = new THREE.Group();
489
-
490
- // Minimalistic weapon shape
491
- const barrel = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 1.8), new THREE.MeshStandardMaterial({ color: gun.color, metalness: 0.9 }));
492
- wGroup.add(barrel);
493
- const grip = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.5, 0.3), new THREE.MeshStandardMaterial({ color: 0x111111 }));
494
- grip.position.set(0, -0.3, -0.4);
495
- wGroup.add(grip);
496
-
497
- // Floating Yellow Halo ring
498
- const halo = new THREE.Mesh(new THREE.RingGeometry(1.2, 1.4, 16), new THREE.MeshBasicMaterial({ color: 0xffd700, side: THREE.DoubleSide }));
499
- halo.rotateX(-Math.PI/2);
500
- halo.position.y = -0.7;
501
- wGroup.add(halo);
502
-
503
- wGroup.position.set(x, h, z);
504
- scene.add(wGroup);
505
-
506
- weaponDrops.push({
507
- mesh: wGroup,
508
- gun: gun,
509
- x: x,
510
- z: z,
511
- radius: 2.5
512
- });
513
- }
514
-
515
- // Spawn 80 loot spots
516
- for (let i = 0; i < 80; i++) {
517
- const randGun = GUNS[1 + Math.floor(Math.random() * (GUNS.length - 1))];
518
- const angle = Math.random() * Math.PI * 2;
519
- const d = Math.random() * 280;
520
- const wx = Math.cos(angle) * d;
521
- const wz = Math.sin(angle) * d;
522
- if (getTerrainHeight(wx, wz) > 0) {
523
- spawnWeaponPickup(randGun, wx, wz);
524
- }
525
- }
526
-
527
- // --- CHARACTER CONSTRUCTOR (Realistic anatomical separate limbs) ---
528
- function createAnatomicalCharacter(isPlayer = false) {
529
- const charGroup = new THREE.Group();
530
-
531
- const skinColor = isPlayer ? 0xffdfac : 0xbf8f6f;
532
- const shirtColor = isPlayer ? 0xcc2222 : 0x2255cc; // RED vs BLUE theme
533
-
534
- const skinMat = new THREE.MeshStandardMaterial({ color: skinColor, roughness: 0.8 });
535
- const shirtMat = new THREE.MeshStandardMaterial({ color: shirtColor, roughness: 0.8 });
536
- const pantsMat = new THREE.MeshStandardMaterial({ color: 0x222222, roughness: 0.9 });
537
-
538
- // Torso (Main chest body)
539
- const torso = new THREE.Mesh(new THREE.BoxGeometry(1.5, 2.2, 0.8), shirtMat);
540
- torso.position.y = 3.5;
541
- torso.castShadow = true;
542
- charGroup.add(torso);
543
-
544
- // Head (Sir)
545
- const head = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.9, 0.9), skinMat);
546
- head.position.y = 5.2;
547
- head.castShadow = true;
548
- charGroup.add(head);
549
-
550
- // Cap
551
- const cap = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.25, 1.0), new THREE.MeshStandardMaterial({ color: isPlayer ? 0xffcc00 : 0x111111 }));
552
- cap.position.set(0, 5.7, 0.05);
553
- charGroup.add(cap);
554
-
555
- // Left Hand (Hath 1)
556
- const lHandPivot = new THREE.Group();
557
- lHandPivot.position.set(-1.0, 4.4, 0);
558
- const lHand = new THREE.Mesh(new THREE.BoxGeometry(0.45, 1.8, 0.45), shirtMat);
559
- lHand.position.y = -0.8;
560
- lHand.castShadow = true;
561
- lHandPivot.add(lHand);
562
- charGroup.add(lHandPivot);
563
-
564
- // Right Hand (Hath 2)
565
- const rHandPivot = new THREE.Group();
566
- rHandPivot.position.set(1.0, 4.4, 0);
567
- const rHand = new THREE.Mesh(new THREE.BoxGeometry(0.45, 1.8, 0.45), shirtMat);
568
- rHand.position.y = -0.8;
569
- rHand.castShadow = true;
570
- rHandPivot.add(rHand);
571
- charGroup.add(rHandPivot);
572
-
573
- // Left Leg (Per 1)
574
- const lLegPivot = new THREE.Group();
575
- lLegPivot.position.set(-0.4, 2.4, 0);
576
- const lLeg = new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0, 0.5), pantsMat);
577
- lLeg.position.y = -1.0;
578
- lLeg.castShadow = true;
579
- lLegPivot.add(lLeg);
580
- charGroup.add(lLegPivot);
581
-
582
- // Right Leg (Per 2)
583
- const rLegPivot = new THREE.Group();
584
- rLegPivot.position.set(0.4, 2.4, 0);
585
- const rLeg = new THREE.Mesh(new THREE.BoxGeometry(0.5, 2.0, 0.5), pantsMat);
586
- rLeg.position.y = -1.0;
587
- rLeg.castShadow = true;
588
- rLegPivot.add(rLeg);
589
- charGroup.add(rLegPivot);
590
-
591
- // Handgun slot on right hand pivot for shooting animation
592
- const gunMeshGroup = new THREE.Group();
593
- const nozzle = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.2, 1.2), new THREE.MeshBasicMaterial({ color: 0x111111 }));
594
- nozzle.position.set(0, -0.7, 0.6);
595
- gunMeshGroup.add(nozzle);
596
- rHandPivot.add(gunMeshGroup);
597
- gunMeshGroup.visible = false; // Initially fist
598
-
599
- charGroup.userData = {
600
- lHand: lHandPivot,
601
- rHand: rHandPivot,
602
- lLeg: lLegPivot,
603
- rLeg: rLegPivot,
604
- gun: gunMeshGroup
605
- };
606
-
607
- return charGroup;
608
- }
609
-
610
- // --- ENEMY BOTS (99 Bots scattered inside island) ---
611
- const bots = [];
612
- const botNicknames = ["Ajjubhai94", "Raistar_YT", "Badge99", "GyanGaming", "Lokesh_Gamer", "Total_Army", "Mortal_Op", "Carry_Op", "Scout_Sniper", "AWM_King", "FistGamer", "GrozaGod", "Viper3", "PhantomX", "ApexKill", "BooyahGuru"];
613
-
614
- for (let i = 0; i < 99; i++) {
615
- const botMesh = createAnatomicalCharacter(false);
616
-
617
- const angle = Math.random() * Math.PI * 2;
618
- const dist = 50 + Math.random() * 260;
619
- const bx = Math.cos(angle) * dist;
620
- const bz = Math.sin(angle) * dist;
621
- const by = getTerrainHeight(bx, bz);
622
-
623
- botMesh.position.set(bx, by, bz);
624
- scene.add(botMesh);
625
-
626
- // Give random starter gun to bot
627
- const randomGun = GUNS[1 + Math.floor(Math.random() * (GUNS.length - 2))];
628
- botMesh.userData.gun.visible = true;
629
-
630
- bots.push({
631
- id: i,
632
- name: botNicknames[i % botNicknames.length] + "_" + (10 + Math.floor(Math.random()*89)),
633
- mesh: botMesh,
634
- x: bx,
635
- y: by,
636
- z: bz,
637
- health: 100,
638
- weapon: randomGun,
639
- state: 'wandering',
640
- targetX: bx,
641
- targetZ: bz,
642
- actionTimer: Math.random() * 4,
643
- alive: true,
644
- shootTimer: 1.5,
645
- walkCycle: 0
646
- });
647
- }
648
-
649
- // --- PLAYER CONFIG ---
650
- const playerMesh = createAnatomicalCharacter(true);
651
- scene.add(playerMesh);
652
-
653
- const player = {
654
- mesh: playerMesh,
655
- x: 0,
656
- y: 280, // High sky initial altitude
657
- z: 0,
658
- vx: 0,
659
- vy: 0,
660
- vz: 0,
661
- rotY: 0,
662
- rotX: 0,
663
- isGrounded: false,
664
- walkCycle: 0,
665
- alive: true
666
- };
667
-
668
- // --- BIG AIRPLANE & PARACHUTE BALLOON ---
669
- const airplane = new THREE.Group();
670
- const bodyMesh = new THREE.Mesh(new THREE.CylinderGeometry(5, 5, 40, 8), new THREE.MeshStandardMaterial({ color: 0xfafafa }));
671
- bodyMesh.rotateX(Math.PI / 2);
672
- airplane.add(bodyMesh);
673
- const wingMesh = new THREE.Mesh(new THREE.BoxGeometry(55, 1, 10), new THREE.MeshStandardMaterial({ color: 0xdddddd }));
674
- airplane.add(wingMesh);
675
- airplane.position.set(-450, 260, 0);
676
- scene.add(airplane);
677
-
678
- // Parachute Balloon
679
- const balloon = new THREE.Group();
680
- const canopy = new THREE.Mesh(new THREE.SphereGeometry(5, 16, 12), new THREE.MeshStandardMaterial({ color: 0xff4400 }));
681
- canopy.scale.set(1.4, 0.7, 1.4);
682
- canopy.position.y = 11;
683
- balloon.add(canopy);
684
-
685
- // Ropes for canopy
686
- const lineMat = new THREE.LineBasicMaterial({ color: 0xffffff });
687
- for (let angle = 0; angle < Math.PI * 2; angle += Math.PI / 2) {
688
- const rx = Math.cos(angle) * 4;
689
- const rz = Math.sin(angle) * 4;
690
- const points = [new THREE.Vector3(0, 3, 0), new THREE.Vector3(rx, 9, rz)];
691
- const ropeGeom = new THREE.BufferGeometry().setFromPoints(points);
692
- balloon.add(new THREE.Line(ropeGeom, lineMat));
693
- }
694
- scene.add(balloon);
695
- balloon.visible = false;
696
-
697
- // --- 3D BULLET TRACER & SPARK EFFECT ENGINE ---
698
- const sparkParticles = [];
699
- const sparkGeom = new THREE.BufferGeometry();
700
- const maxSparks = 120;
701
- const sparkPos = new Float32Array(maxSparks * 3);
702
- const sparkCol = new Float32Array(maxSparks * 3);
703
-
704
- for (let i = 0; i < maxSparks * 3; i++) {
705
- sparkPos[i] = 9999; // offscreen
706
- sparkCol[i] = 1;
707
- }
708
- sparkGeom.setAttribute('position', new THREE.BufferAttribute(sparkPos, 3));
709
- const sparkSystem = new THREE.Points(sparkGeom, new THREE.PointsMaterial({
710
- size: 0.6,
711
- vertexColors: false,
712
- color: 0xffa500, // orange-yellow sparks
713
- transparent: true,
714
- blending: THREE.AdditiveBlending,
715
- depthWrite: false
716
- }));
717
- scene.add(sparkSystem);
718
-
719
- function triggerMuzzleSparks(origin, dir) {
720
- for (let i = 0; i < 8; i++) {
721
- sparkParticles.push({
722
- x: origin.x,
723
- y: origin.y,
724
- z: origin.z,
725
- vx: (dir.x + (Math.random() - 0.5) * 0.5) * 9,
726
- vy: (dir.y + (Math.random() - 0.5) * 0.5) * 9,
727
- vz: (dir.z + (Math.random() - 0.5) * 0.5) * 9,
728
- life: 1.0,
729
- decay: 0.08 + Math.random() * 0.1
730
- });
731
- }
732
- }
733
-
734
- // Visual tracer bullet lines
735
- function drawBulletTracer(start, end) {
736
- const points = [start, end];
737
- const tGeom = new THREE.BufferGeometry().setFromPoints(points);
738
- const tMat = new THREE.LineBasicMaterial({ color: 0xffea00, transparent: true, opacity: 0.9, linewidth: 2 });
739
- const tracer = new THREE.Line(tGeom, tMat);
740
- scene.add(tracer);
741
-
742
- // Remove tracer line after a few frames
743
- setTimeout(() => {
744
- scene.remove(tracer);
745
- tGeom.dispose();
746
- tMat.dispose();
747
- }, 60);
748
- }
749
-
750
- function updateSparks(delta) {
751
- const pAttr = sparkGeom.attributes.position;
752
- for (let i = 0; i < maxSparks; i++) {
753
- const p = sparkParticles[i];
754
- if (p && p.life > 0) {
755
- p.x += p.vx * delta;
756
- p.y += p.vy * delta;
757
- p.z += p.vz * delta;
758
- p.life -= p.decay;
759
- pAttr.setXYZ(i, p.x, p.y, p.z);
760
- } else {
761
- pAttr.setXYZ(i, 9999, 9999, 9999);
762
- }
763
- }
764
- pAttr.needsUpdate = true;
765
- }
766
-
767
- // --- SAFE DOME ZONE ---
768
- const dome = new THREE.Mesh(
769
- new THREE.CylinderGeometry(1, 1, 400, 24, 1, true),
770
- new THREE.MeshBasicMaterial({ color: 0x33b5e5, transparent: true, opacity: 0.15, wireframe: true, side: THREE.DoubleSide })
771
- );
772
- scene.add(dome);
773
-
774
- // --- MOBILE CONSOLE INPUTS (Smooth Touch Dragging) ---
775
- const lookPad = document.getElementById('lookTouchPad');
776
- let isDraggingLook = false;
777
- let lastLookTouchX = 0;
778
- let lastLookTouchY = 0;
779
-
780
- lookPad.addEventListener('touchstart', (e) => {
781
- const touch = e.touches[0];
782
- isDraggingLook = true;
783
- lastLookTouchX = touch.clientX;
784
- lastLookTouchY = touch.clientY;
785
- initAudio(); // triggers sound context
786
- });
787
-
788
- lookPad.addEventListener('touchmove', (e) => {
789
- if (!isDraggingLook) return;
790
- const touch = e.touches[0];
791
- const dx = touch.clientX - lastLookTouchX;
792
- const dy = touch.clientY - lastLookTouchY;
793
-
794
- player.rotY -= dx * 0.005; // look sensitivity
795
- player.rotX -= dy * 0.005;
796
- player.rotX = Math.max(-Math.PI / 3.5, Math.min(Math.PI / 3.5, player.rotX));
797
-
798
- lastLookTouchX = touch.clientX;
799
- lastLookTouchY = touch.clientY;
800
- });
801
-
802
- lookPad.addEventListener('touchend', () => {
803
- isDraggingLook = false;
804
- });
805
-
806
- // Virtual Dynamic Joystick
807
- const joyArea = document.getElementById('joystickArea');
808
- const joyKnob = document.getElementById('joystickKnob');
809
- let joyTouchId = null;
810
- let joyStartX = 0;
811
- let joyStartY = 0;
812
-
813
- joyArea.addEventListener('touchstart', (e) => {
814
- const touch = e.changedTouches[0];
815
- joyTouchId = touch.identifier;
816
- const rect = joyArea.getBoundingClientRect();
817
- joyStartX = rect.left + rect.width / 2;
818
- joyStartY = rect.top + rect.height / 2;
819
- state.joy.active = true;
820
- initAudio();
821
- });
822
-
823
- joyArea.addEventListener('touchmove', (e) => {
824
- if (joyTouchId === null) return;
825
- let currentTouch = null;
826
- for (let i = 0; i < e.touches.length; i++) {
827
- if (e.touches[i].identifier === joyTouchId) {
828
- currentTouch = e.touches[i];
829
- break;
830
- }
831
- }
832
- if (!currentTouch) return;
833
-
834
- const dx = currentTouch.clientX - joyStartX;
835
- const dy = currentTouch.clientY - joyStartY;
836
- const dist = Math.sqrt(dx*dx + dy*dy);
837
- const maxRadius = 38;
838
-
839
- if (dist > 0) {
840
- const nx = dx / dist;
841
- const ny = dy / dist;
842
- const clampedDist = Math.min(dist, maxRadius);
843
-
844
- state.joy.dx = nx * (clampedDist / maxRadius);
845
- state.joy.dy = ny * (clampedDist / maxRadius);
846
-
847
- joyKnob.style.transform = `translate(${state.joy.dx * 30}px, ${state.joy.dy * 30}px)`;
848
- }
849
- });
850
-
851
- const resetJoystick = () => {
852
- joyTouchId = null;
853
- state.joy.active = false;
854
- state.joy.dx = 0;
855
- state.joy.dy = 0;
856
- joyKnob.style.transform = 'translate(0px, 0px)';
857
- };
858
-
859
- joyArea.addEventListener('touchend', resetJoystick);
860
- joyArea.addEventListener('touchcancel', resetJoystick);
861
-
862
- // Action Buttons
863
- const actionBtn = document.getElementById('actionBtn');
864
- actionBtn.addEventListener('click', () => {
865
- initAudio();
866
- if (state.phase === 'plane') {
867
- state.phase = 'skydiving';
868
- player.x = airplane.position.x;
869
- player.z = airplane.position.z;
870
- player.y = 260;
871
- player.vy = -3.8; // Skydive descent
872
-
873
- actionBtn.innerText = '🎈 DEPLOY BALLOON (PARACHUTE)';
874
- document.getElementById('screenTitle').innerText = 'SKYDIVING';
875
- document.getElementById('screenSubtitle').innerText = 'Zameen ke paas aakar balloon deploy karein.';
876
- } else if (state.phase === 'skydiving') {
877
- state.phase = 'parachute';
878
- player.vy = -0.9; // Safe descent
879
- balloon.visible = true;
880
- actionBtn.style.display = 'none';
881
- }
882
- });
883
-
884
- // PC Controls support
885
- window.addEventListener('keydown', (e) => {
886
- const k = e.key.toLowerCase();
887
- if (k === 'w' || e.key === 'ArrowUp') state.keys.w = true;
888
- if (k === 's' || e.key === 'ArrowDown') state.keys.s = true;
889
- if (k === 'a' || e.key === 'ArrowLeft') state.keys.a = true;
890
- if (k === 'd' || e.key === 'ArrowRight') state.keys.d = true;
891
- if (e.key === ' ') {
892
- if (player.isGrounded && state.phase === 'ground') {
893
- player.vy = 1.6;
894
- player.isGrounded = false;
895
- }
896
- }
897
- });
898
-
899
- window.addEventListener('keyup', (e) => {
900
- const k = e.key.toLowerCase();
901
- if (k === 'w' || e.key === 'ArrowUp') state.keys.w = false;
902
- if (k === 's' || e.key === 'ArrowDown') state.keys.s = false;
903
- if (k === 'a' || e.key === 'ArrowLeft') state.keys.a = false;
904
- if (k === 'd' || e.key === 'ArrowRight') state.keys.d = false;
905
- });
906
-
907
- // PointerLock details for desktop click look
908
- window.addEventListener('click', () => {
909
- if (state.phase === 'ground' && window.innerWidth > 900) {
910
- renderer.domElement.requestPointerLock();
911
- }
912
- });
913
-
914
- window.addEventListener('mousemove', (e) => {
915
- if (document.pointerLockElement === renderer.domElement) {
916
- player.rotY -= e.movementX * 0.0025;
917
- player.rotX -= e.movementY * 0.0025;
918
- player.rotX = Math.max(-Math.PI / 3.5, Math.min(Math.PI / 3.5, player.rotX));
919
- }
920
- });
921
-
922
- // --- GUARANTEED FIRING & ASSIST AIM ---
923
- function executeWeaponShoot() {
924
- if (state.phase !== 'ground' || !player.alive) return;
925
- const now = performance.now();
926
- const gun = state.currentWeapon;
927
-
928
- if (gun.ammo !== '∞' && gun.ammo <= 0) {
929
- pushCombatLog("⚠️ AMMO KHATAM! Dusra Weapon looto!");
930
- return;
931
- }
932
-
933
- if (now - state.lastFired < 250) return; // rate of fire
934
- state.lastFired = now;
935
-
936
- // Reduce ammo
937
- if (gun.ammo !== '∞') {
938
- gun.ammo--;
939
- document.getElementById('gunAmmo').innerText = `${gun.ammo} / ${gun.maxAmmo}`;
940
- }
941
-
942
- // Play synthesized fire audio
943
- playShootSound();
944
-
945
- // Flash & Tracer directions
946
- const shootOrigin = playerMesh.position.clone().add(new THREE.Vector3(0, 4, 0));
947
-
948
- // Aiming vector matching camera look directions
949
- const targetDir = new THREE.Vector3(
950
- -Math.sin(player.rotY) * Math.cos(player.rotX),
951
- Math.sin(player.rotX),
952
- -Math.cos(player.rotY) * Math.cos(player.rotX)
953
- ).normalize();
954
-
955
- triggerMuzzleSparks(shootOrigin, targetDir);
956
-
957
- // Assist Aim target check (Shoot closest bot within range and field of view)
958
- let finalHitPoint = shootOrigin.clone().add(targetDir.clone().multiplyScalar(gun.range * 4));
959
- let closestBot = null;
960
- let minDistance = gun.range * 4;
961
-
962
- bots.forEach(bot => {
963
- if (!bot.alive) return;
964
- const d = Math.sqrt((bot.x - player.x)**2 + (bot.z - player.z)**2);
965
- if (d < minDistance) {
966
- // Angle check to see if bot is generally in front of player
967
- const toBot = new THREE.Vector3(bot.x - player.x, 0, bot.z - player.z).normalize();
968
- const lookFlat = new THREE.Vector3(-Math.sin(player.rotY), 0, -Math.cos(player.rotY)).normalize();
969
- const dot = toBot.dot(lookFlat);
970
-
971
- if (dot > 0.82) { // Target lock cone
972
- closestBot = bot;
973
- minDistance = d;
974
- }
975
- }
976
- });
977
-
978
- if (closestBot) {
979
- // Gun hits the enemy!
980
- finalHitPoint.set(closestBot.x, closestBot.y + 3, closestBot.z);
981
- closestBot.health -= gun.damage;
982
- playHitSound();
983
-
984
- // Blood splat sparks
985
- triggerMuzzleSparks(finalHitPoint, targetDir.clone().multiplyScalar(-0.6));
986
-
987
- if (closestBot.health <= 0) {
988
- closestBot.alive = false;
989
- closestBot.mesh.visible = false;
990
- state.kills++;
991
- state.playersCount--;
992
-
993
- document.getElementById('killCounter').innerText = state.kills;
994
- document.getElementById('aliveCounter').innerText = state.playersCount;
995
-
996
- pushCombatLog(`💀 Aapne ${closestBot.name} ko DEFEAT kiya!`);
997
-
998
- // Drops bot's gun on ground
999
- spawnWeaponPickup(closestBot.weapon, closestBot.x, closestBot.z);
1000
- } else {
1001
- pushCombatLog(`🎯 Hit: -${gun.damage} HP to ${closestBot.name}`);
1002
- }
1003
- }
1004
-
1005
- // Draw physical visual tracer laser!
1006
- drawBulletTracer(shootOrigin, finalHitPoint);
1007
- }
1008
-
1009
- // Connect Firing buttons
1010
- document.getElementById('mobileFireBtn').addEventListener('touchstart', (e) => {
1011
- e.preventDefault();
1012
- initAudio();
1013
- executeWeaponShoot();
1014
- });
1015
-
1016
- document.getElementById('mobileJumpBtn').addEventListener('touchstart', (e) => {
1017
- e.preventDefault();
1018
- if (player.isGrounded && state.phase === 'ground') {
1019
- player.vy = 1.6;
1020
- player.isGrounded = false;
1021
- }
1022
- });
1023
-
1024
- // --- MINIMAP RENDER ---
1025
- const mapCanvas = document.getElementById('minimapCanvas');
1026
- const mapCtx = mapCanvas.getContext('2d');
1027
-
1028
- function drawMinimap() {
1029
- mapCtx.fillStyle = '#2d5a27'; // island background
1030
- mapCtx.fillRect(0, 0, 80, 80);
1031
-
1032
- const scale = 80 / MAP_SIZE;
1033
- const cx = (val) => (val + MAP_SIZE/2) * scale;
1034
-
1035
- // Safe Zone Circle blue ring
1036
- mapCtx.strokeStyle = '#00d2ff';
1037
- mapCtx.lineWidth = 1.5;
1038
- mapCtx.beginPath();
1039
- mapCtx.arc(cx(state.zone.x), cx(state.zone.z), state.zone.radius * scale, 0, Math.PI*2);
1040
- mapCtx.stroke();
1041
-
1042
- // Player dot (Yellow)
1043
- mapCtx.fillStyle = '#ffeb3b';
1044
- mapCtx.beginPath();
1045
- mapCtx.arc(cx(player.x), cx(player.z), 3.5, 0, Math.PI*2);
1046
- mapCtx.fill();
1047
-
1048
- // Nearby active Bots (Red)
1049
- mapCtx.fillStyle = '#f44336';
1050
- bots.forEach(bot => {
1051
- if (bot.alive) {
1052
- const dist = Math.sqrt((bot.x - player.x)**2 + (bot.z - player.z)**2);
1053
- if (dist < 150) {
1054
- mapCtx.beginPath();
1055
- mapCtx.arc(cx(bot.x), cx(bot.z), 2, 0, Math.PI*2);
1056
- mapCtx.fill();
1057
- }
1058
- }
1059
- });
1060
- }
1061
-
1062
- // --- GAME LOG FEED ---
1063
- function pushCombatLog(msg) {
1064
- const feed = document.getElementById('killFeed');
1065
- const item = document.createElement('div');
1066
- item.className = "bg-black/80 text-white border-l-4 border-yellow-500 text-[10px] px-2.5 py-1 rounded font-sans font-bold shadow-lg animate-pulse";
1067
- item.innerText = msg;
1068
-
1069
- feed.appendChild(item);
1070
- if (feed.children.length > 3) {
1071
- feed.removeChild(feed.children[0]);
1072
- }
1073
- setTimeout(() => { if (item.parentNode) item.remove(); }, 4000);
1074
- }
1075
-
1076
- // --- GAME OVER CONDITIONS ---
1077
- function triggerMatchOver(isVictory, killer = "Zone Danger") {
1078
- state.isGameOver = true;
1079
- document.getElementById('gameOverScreen').classList.remove('hidden');
1080
-
1081
- const title = document.getElementById('gameOverTitle');
1082
- const sub = document.getElementById('gameOverSub');
1083
- document.getElementById('goKills').innerText = state.kills;
1084
- document.getElementById('goRank').innerText = `#${state.playersCount + 1} / 100`;
1085
-
1086
- if (isVictory) {
1087
- title.innerText = "🏆 BOOYAH! 🏆";
1088
- title.className = "text-5xl font-black text-green-400 tracking-wider mb-2 animate-bounce";
1089
- sub.innerText = "Aapne island par aakhiri bache huye dushman ko maar kar vijay pai!";
1090
- } else {
1091
- title.innerText = "ELIMINATED";
1092
- title.className = "text-5xl font-black text-red-600 tracking-wider mb-2";
1093
- sub.innerText = `Aapko ${killer} ne match se nikal diya!`;
1094
- }
1095
- }
1096
-
1097
- // --- SIMULATE BOT COMBAT ENGINE ---
1098
- function processBotAI(delta) {
1099
- bots.forEach(bot => {
1100
- if (!bot.alive) return;
1101
-
1102
- bot.actionTimer -= delta;
1103
- if (bot.actionTimer <= 0) {
1104
- bot.actionTimer = 2.0 + Math.random() * 3.5;
1105
-
1106
- // Choose state
1107
- const r = Math.random();
1108
- if (r < 0.5) {
1109
- bot.state = 'wandering';
1110
- bot.targetX = state.zone.x + (Math.random() - 0.5) * state.zone.radius;
1111
- bot.targetZ = state.zone.z + (Math.random() - 0.5) * state.zone.radius;
1112
- } else {
1113
- // Look for closest target
1114
- let target = null;
1115
- let dMin = 180;
1116
-
1117
- // Dist to player
1118
- const dPlayer = Math.sqrt((player.x - bot.x)**2 + (player.z - bot.z)**2);
1119
- if (dPlayer < dMin && player.alive && state.phase === 'ground') {
1120
- target = player;
1121
- dMin = dPlayer;
1122
- }
1123
-
1124
- // Dist to other bots
1125
- bots.forEach(other => {
1126
- if (other.id !== bot.id && other.alive) {
1127
- const dbb = Math.sqrt((other.x - bot.x)**2 + (other.z - bot.z)**2);
1128
- if (dbb < dMin) {
1129
- target = other;
1130
- dMin = dbb;
1131
- }
1132
- }
1133
- });
1134
-
1135
- if (target) {
1136
- bot.state = (dMin < bot.weapon.range * 4) ? 'shooting' : 'chasing';
1137
- bot.targetX = target.mesh.position.x;
1138
- bot.targetZ = target.mesh.position.z;
1139
- }
1140
- }
1141
- }
1142
-
1143
- // Execute states
1144
- if (bot.state === 'wandering' || bot.state === 'chasing') {
1145
- const dx = bot.targetX - bot.x;
1146
- const dz = bot.targetZ - bot.z;
1147
- const len = Math.sqrt(dx*dx + dz*dz);
1148
-
1149
- if (len > 3) {
1150
- const step = 14 * delta;
1151
- bot.x += (dx / len) * step;
1152
- bot.z += (dz / len) * step;
1153
- bot.y = getTerrainHeight(bot.x, bot.z);
1154
-
1155
- // Rotate look
1156
- bot.mesh.rotation.y = Math.atan2(dx, dz);
1157
-
1158
- // Walk limbs loop
1159
- bot.walkCycle += delta * 14;
1160
- const swing = Math.sin(bot.walkCycle) * 0.6;
1161
- bot.mesh.userData.lLeg.rotation.x = swing;
1162
- bot.mesh.userData.rLeg.rotation.x = -swing;
1163
- bot.mesh.userData.lHand.rotation.x = -swing;
1164
- bot.mesh.userData.rHand.rotation.x = swing;
1165
- }
1166
- } else if (bot.state === 'shooting') {
1167
- bot.shootTimer -= delta;
1168
- if (bot.shootTimer <= 0) {
1169
- bot.shootTimer = 1.2 + Math.random() * 1.4;
1170
-
1171
- // Shoot particle effects towards targets
1172
- const dir = new THREE.Vector3(bot.targetX - bot.x, 0, bot.targetZ - bot.z).normalize();
1173
- triggerMuzzleSparks(bot.mesh.position.clone().add(new THREE.Vector3(0,4,0)), dir);
1174
-
1175
- // Simulate Hit chances
1176
- if (Math.random() > 0.6) {
1177
- const dP = Math.sqrt((player.x - bot.x)**2 + (player.z - bot.z)**2);
1178
- if (dP < bot.weapon.range * 4 && player.alive && state.phase === 'ground') {
1179
- // Hit Player!
1180
- const dmg = Math.floor(bot.weapon.damage * 0.4); // slightly padded for easy playability
1181
- state.health -= dmg;
1182
- pushCombatLog(`⚠️ Warning: ${bot.name} ne aapko damage diya! (-${dmg} HP)`);
1183
- document.getElementById('hpBar').style.width = `${(state.health / state.maxHealth)*100}%`;
1184
- document.getElementById('hpValue').innerText = `${Math.ceil(state.health)} / 200 HP`;
1185
-
1186
- if (state.health <= 0) {
1187
- triggerMatchOver(false, bot.name);
1188
- }
1189
- } else {
1190
- // Bots eliminate other bots background simulations
1191
- bots.forEach(other => {
1192
- if (other.id !== bot.id && other.alive && Math.random() > 0.8) {
1193
- other.health -= bot.weapon.damage;
1194
- if (other.health <= 0) {
1195
- other.alive = false;
1196
- other.mesh.visible = false;
1197
- state.playersCount--;
1198
- document.getElementById('aliveCounter').innerText = state.playersCount;
1199
- pushCombatLog(`💀 ${bot.name} ne ${other.name} ko eliminate kiya`);
1200
- }
1201
- }
1202
- });
1203
- }
1204
- }
1205
- }
1206
- }
1207
-
1208
- bot.mesh.position.set(bot.x, bot.y, bot.z);
1209
- });
1210
- }
1211
-
1212
- // --- UPDATE SAFE ZONE TRACKING ---
1213
- function updateSafeZone(delta) {
1214
- state.zone.shrinkTimer -= delta;
1215
- if (state.zone.shrinkTimer <= 0) {
1216
- state.zone.isActive = true;
1217
- state.zone.targetRadius = Math.max(40, state.zone.radius - 70);
1218
- state.zone.shrinkTimer = 40;
1219
-
1220
- document.getElementById('dangerAlert').classList.remove('hidden');
1221
- setTimeout(() => { document.getElementById('dangerAlert').classList.add('hidden'); }, 4000);
1222
- }
1223
-
1224
- if (state.zone.isActive && state.zone.radius > state.zone.targetRadius) {
1225
- state.zone.radius -= 4 * delta;
1226
- }
1227
-
1228
- document.getElementById('zoneTimerText').innerText = `Next Zone: ${Math.ceil(state.zone.shrinkTimer)}s`;
1229
-
1230
- // Scaled cylindrical dome
1231
- dome.position.set(state.zone.x, 0, state.zone.z);
1232
- dome.scale.set(state.zone.radius, 1, state.zone.radius);
1233
-
1234
- // Check if player is outside dome radius
1235
- const dist = Math.sqrt((player.x - state.zone.x)**2 + (player.z - state.zone.z)**2);
1236
- if (dist > state.zone.radius && state.phase === 'ground') {
1237
- state.health -= 6 * delta; // continuous tick damage
1238
- document.getElementById('hpBar').style.width = `${(state.health / state.maxHealth)*100}%`;
1239
- document.getElementById('hpValue').innerText = `${Math.ceil(state.health)} / 200 HP`;
1240
-
1241
- if (state.health <= 0) {
1242
- triggerMatchOver(false, "Zone Damage");
1243
- }
1244
- }
1245
- }
1246
-
1247
- // --- MAIN ENGINE RENDER LOOP ---
1248
- let prevTime = performance.now();
1249
-
1250
- function mainLoop() {
1251
- if (state.isGameOver) return;
1252
-
1253
- requestAnimationFrame(mainLoop);
1254
-
1255
- const now = performance.now();
1256
- const delta = (now - prevTime) / 1000;
1257
- prevTime = now;
1258
-
1259
- // --- 1. PLANE PHASE ANIMATION ---
1260
- if (state.phase === 'plane') {
1261
- airplane.position.x += 35 * delta;
1262
- if (airplane.position.x > MAP_SIZE / 2 + 100) {
1263
- airplane.position.x = -MAP_SIZE / 2 - 100;
1264
- }
1265
- camera.position.set(airplane.position.x, airplane.position.y + 35, airplane.position.z + 75);
1266
- camera.lookAt(airplane.position);
1267
- }
1268
-
1269
- // --- 2. SKYDIVING PHASE ---
1270
- else if (state.phase === 'skydiving') {
1271
- player.y += player.vy * 10 * delta;
1272
-
1273
- // Steer falling using keys or joystick touch
1274
- let sx = 0, sz = 0;
1275
- if (state.keys.w) sz = -1;
1276
- if (state.keys.s) sz = 1;
1277
- if (state.keys.a) sx = -1;
1278
- if (state.keys.d) sx = 1;
1279
-
1280
- if (state.joy.active) {
1281
- sx = state.joy.dx;
1282
- sz = state.joy.dy;
1283
- }
1284
-
1285
- player.x += sx * 22 * delta;
1286
- player.z += sz * 22 * delta;
1287
-
1288
- playerMesh.position.set(player.x, player.y, player.z);
1289
- playerMesh.rotation.z = Math.sin(now * 0.01) * 0.15;
1290
-
1291
- // Follow camera
1292
- camera.position.set(player.x, player.y + 20, player.z + 35);
1293
- camera.lookAt(playerMesh.position);
1294
-
1295
- // Auto deploy balloon
1296
- if (player.y < 70) {
1297
- actionBtn.click();
1298
- }
1299
- }
1300
-
1301
- // --- 3. PARACHUTE DESCEND ---
1302
- else if (state.phase === 'parachute') {
1303
- player.y += player.vy * 10 * delta;
1304
-
1305
- let sx = 0, sz = 0;
1306
- if (state.keys.w) sz = -1;
1307
- if (state.keys.s) sz = 1;
1308
- if (state.keys.a) sx = -1;
1309
- if (state.keys.d) sx = 1;
1310
-
1311
- if (state.joy.active) {
1312
- sx = state.joy.dx;
1313
- sz = state.joy.dy;
1314
- }
1315
-
1316
- player.x += sx * 10 * delta;
1317
- player.z += sz * 10 * delta;
1318
-
1319
- playerMesh.position.set(player.x, player.y, player.z);
1320
-
1321
- balloon.visible = true;
1322
- balloon.position.set(player.x, player.y, player.z);
1323
-
1324
- camera.position.set(player.x, player.y + 15, player.z + 28);
1325
- camera.lookAt(playerMesh.position);
1326
-
1327
- // Land contact
1328
- const landY = getTerrainHeight(player.x, player.z);
1329
- if (player.y <= landY + 1.2) {
1330
- state.phase = 'ground';
1331
- player.y = landY;
1332
- player.vy = 0;
1333
- player.isGrounded = true;
1334
- balloon.visible = false;
1335
-
1336
- document.getElementById('crosshair').classList.remove('hidden');
1337
- document.getElementById('screenTitle').style.display = 'none';
1338
- document.getElementById('screenSubtitle').style.display = 'none';
1339
- document.getElementById('joystickGuide').classList.add('hidden');
1340
- }
1341
- }
1342
-
1343
- // --- 4. COMBAT IS ACTIVE ---
1344
- else if (state.phase === 'ground') {
1345
- const stepVec = new THREE.Vector3(0,0,0);
1346
- if (state.keys.w) stepVec.z -= 1;
1347
- if (state.keys.s) stepVec.z += 1;
1348
- if (state.keys.a) stepVec.x -= 1;
1349
- if (state.keys.d) stepVec.x += 1;
1350
-
1351
- if (state.joy.active) {
1352
- stepVec.x = state.joy.dx;
1353
- stepVec.z = state.joy.dy;
1354
- }
1355
-
1356
- stepVec.normalize();
1357
- // Rotate walking direction based on look directions
1358
- stepVec.applyAxisAngle(new THREE.Vector3(0, 1, 0), player.rotY);
1359
-
1360
- const walkSpeed = 24 * delta;
1361
- player.x += stepVec.x * walkSpeed;
1362
- player.z += stepVec.z * walkSpeed;
1363
-
1364
- // Handle ground jump mechanics
1365
- if (!player.isGrounded) {
1366
- player.vy -= 4.5 * delta;
1367
- player.y += player.vy;
1368
- const currentLandH = getTerrainHeight(player.x, player.z);
1369
- if (player.y <= currentLandH) {
1370
- player.y = currentLandH;
1371
- player.vy = 0;
1372
- player.isGrounded = true;
1373
- }
1374
- } else {
1375
- player.y = getTerrainHeight(player.x, player.z);
1376
- }
1377
-
1378
- playerMesh.position.set(player.x, player.y, player.z);
1379
- playerMesh.rotation.y = player.rotY;
1380
-
1381
- // Legs walking visual swing loops
1382
- if (stepVec.lengthSq() > 0.02) {
1383
- player.walkCycle += delta * 15;
1384
- const amp = Math.sin(player.walkCycle) * 0.7;
1385
- playerMesh.userData.lLeg.rotation.x = amp;
1386
- playerMesh.userData.rLeg.rotation.x = -amp;
1387
- playerMesh.userData.lHand.rotation.x = -amp;
1388
- playerMesh.userData.rHand.rotation.x = amp;
1389
- } else {
1390
- playerMesh.userData.lLeg.rotation.x = 0;
1391
- playerMesh.userData.rLeg.rotation.x = 0;
1392
- playerMesh.userData.lHand.rotation.x = 0;
1393
- playerMesh.userData.rHand.rotation.x = 0;
1394
- }
1395
-
1396
- // Smooth camera follow offset
1397
- const offsetDistance = 12;
1398
- const camX = player.x - Math.sin(player.rotY) * offsetDistance;
1399
- const camZ = player.z - Math.cos(player.rotY) * offsetDistance;
1400
- const camY = player.y + 5.0 + (Math.sin(player.rotX) * 4);
1401
-
1402
- camera.position.set(camX, camY, camZ);
1403
- camera.lookAt(player.x, player.y + 3.5, player.z);
1404
-
1405
- // Check and collect loot weapon drops
1406
- weaponDrops.forEach((pickup) => {
1407
- if (pickup.mesh.visible) {
1408
- const dist = Math.sqrt((pickup.x - player.x)**2 + (pickup.z - player.z)**2);
1409
- if (dist < pickup.radius) {
1410
- pickup.mesh.visible = false;
1411
-
1412
- // Loot Weapon
1413
- state.currentWeapon = pickup.gun;
1414
- playerMesh.userData.gun.visible = true; // render inside hand
1415
-
1416
- document.getElementById('gunName').innerText = pickup.gun.name;
1417
- document.getElementById('gunAmmo').innerText = `${pickup.gun.ammo} / ${pickup.gun.maxAmmo}`;
1418
- document.getElementById('gunTypeLabel').innerText = pickup.gun.type;
1419
-
1420
- pushCombatLog(`🔫 Looted: ${pickup.gun.name}`);
1421
- }
1422
- }
1423
- });
1424
-
1425
- // Update Zone & FX
1426
- updateSafeZone(delta);
1427
- updateSparks(delta);
1428
-
1429
- // Victory evaluation
1430
- if (state.playersCount <= 1 && player.alive) {
1431
- triggerMatchOver(true);
1432
- }
1433
- }
1434
-
1435
- // Move bot behaviors
1436
- processBotAI(delta);
1437
-
1438
- // Redraw mini map radar
1439
- drawMinimap();
1440
-
1441
- renderer.render(scene, camera);
1442
- }
1443
-
1444
- // Run match
1445
- window.onload = function() {
1446
- mainLoop();
1447
- };
1448
-
1449
- // Window resize
1450
- window.addEventListener('resize', () => {
1451
- camera.aspect = window.innerWidth / window.innerHeight;
1452
- camera.updateProjectionMatrix();
1453
- renderer.setSize(window.innerWidth, window.innerHeight);
1454
- });
1455
-
1456
- </script>
1457
- </body>
1458
- </html>