Offex commited on
Commit
d27be14
·
verified ·
1 Parent(s): 86c1872

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1507 -19
index.html CHANGED
@@ -1,19 +1,1507 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
6
+ <title>Free Fire - 3D Battle Royale Chrome Edition</title>
7
+ <!-- Tailwind CSS for modern UI -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- Three.js Library -->
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
+ background-color: #050505;
20
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
21
+ }
22
+ #gameCanvas {
23
+ width: 100%;
24
+ height: 100%;
25
+ display: block;
26
+ }
27
+ /* Mobile Joystick Styles */
28
+ .joystick-container {
29
+ width: 120px;
30
+ height: 120px;
31
+ background: rgba(255, 255, 255, 0.15);
32
+ border: 2px solid rgba(255, 255, 255, 0.4);
33
+ border-radius: 50%;
34
+ position: absolute;
35
+ bottom: 30px;
36
+ left: 30px;
37
+ touch-action: none;
38
+ display: none; /* Desktop par hidden, mobile touch par show */
39
+ justify-content: center;
40
+ align-items: center;
41
+ z-index: 50;
42
+ }
43
+ .joystick-knob {
44
+ width: 50px;
45
+ height: 50px;
46
+ background: rgba(255, 255, 255, 0.8);
47
+ border-radius: 50%;
48
+ box-shadow: 0 0 10px rgba(0,0,0,0.5);
49
+ }
50
+ /* Crosshair */
51
+ #crosshair {
52
+ position: absolute;
53
+ top: 50%;
54
+ left: 50%;
55
+ transform: translate(-50%, -50%);
56
+ pointer-events: none;
57
+ z-index: 10;
58
+ display: none; /* Airplane/parachute phase me hidden */
59
+ }
60
+ </style>
61
+ </head>
62
+ <body>
63
+
64
+ <!-- Main Game Canvas -->
65
+ <canvas id="gameCanvas"></canvas>
66
+
67
+ <!-- Crosshair -->
68
+ <div id="crosshair">
69
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ff0000" stroke-width="2">
70
+ <circle cx="12" cy="12" r="3" />
71
+ <line x1="12" y1="2" x2="12" y2="7" />
72
+ <line x1="12" y1="17" x2="12" y2="22" />
73
+ <line x1="2" y1="12" x2="7" y2="12" />
74
+ <line x1="17" y1="12" x2="22" y2="12" />
75
+ </svg>
76
+ </div>
77
+
78
+ <!-- UI Overlay -->
79
+ <div id="uiOverlay" class="absolute inset-0 pointer-events-none flex flex-col justify-between p-4 text-white">
80
+
81
+ <!-- Header: Alive count, Kills, Minimap -->
82
+ <div class="flex justify-between items-start w-full">
83
+ <!-- Left Side: Game Stats -->
84
+ <div class="bg-black/60 backdrop-blur-md p-3 rounded-xl border border-yellow-500/30 flex flex-col gap-1 pointer-events-auto">
85
+ <div class="flex items-center gap-2">
86
+ <span class="text-xs text-gray-400">ALIVE:</span>
87
+ <span id="aliveCounter" class="text-xl font-black text-yellow-400">100</span>
88
+ </div>
89
+ <div class="flex items-center gap-2">
90
+ <span class="text-xs text-gray-400">KILLS:</span>
91
+ <span id="killCounter" class="text-xl font-black text-red-500">0</span>
92
+ </div>
93
+ <div class="text-[10px] text-gray-300 font-mono mt-1" id="zoneTimerText">Next Safe Zone: 60s</div>
94
+ </div>
95
+
96
+ <!-- Center Notification (Kill Feed) -->
97
+ <div class="flex flex-col items-center gap-2 max-w-xs mt-2" id="killFeed">
98
+ <!-- Notifications dynamically added here -->
99
+ </div>
100
+
101
+ <!-- Right Side: Mini Map and Phase Status -->
102
+ <div class="flex flex-col items-end gap-2">
103
+ <div class="relative w-28 h-28 bg-black/80 rounded-lg border-2 border-yellow-500 overflow-hidden pointer-events-auto shadow-lg shadow-black/50">
104
+ <!-- Minimap Canvas representing safe zone and players -->
105
+ <canvas id="minimapCanvas" width="112" height="112"></canvas>
106
+ </div>
107
+ <div class="bg-black/60 px-3 py-1 rounded text-xs border border-red-500 text-red-400 font-bold animate-pulse hidden" id="dangerAlert">
108
+ ⚠️ DANGER ZONE SHRINKING!
109
+ </div>
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Center Screen Action Prompt (Eject, Deploy Parachute) -->
114
+ <div class="absolute top-1/3 left-1/2 transform -translate-x-1/2 -translate-y-1/2 flex flex-col items-center gap-4 text-center">
115
+ <h1 id="screenTitle" class="text-4xl font-extrabold text-yellow-400 drop-shadow-[0_4px_6px_rgba(0,0,0,0.8)] tracking-wider">BATTLE ROYALE</h1>
116
+ <p id="screenSubtitle" class="text-sm text-gray-200 bg-black/50 px-4 py-1.5 rounded-full drop-shadow">Kool 100 Players, 1 Island. Jeetne ke liye bachein!</p>
117
+
118
+ <!-- Context Buttons -->
119
+ <button id="actionBtn" class="pointer-events-auto mt-4 px-8 py-4 bg-gradient-to-r from-yellow-500 to-orange-600 hover:from-yellow-400 hover:to-orange-500 text-black font-black text-xl rounded-xl shadow-lg shadow-orange-500/30 transform active:scale-95 transition-all flex items-center gap-2 uppercase tracking-widest border border-white/20">
120
+ 🚀 PLANE SE EJECT KAREIN
121
+ </button>
122
+ </div>
123
+
124
+ <!-- Bottom UI: HP, Gun Slots, Controls Info -->
125
+ <div class="w-full flex flex-col md:flex-row justify-between items-end gap-4">
126
+ <!-- Left Corner: Health & Player state -->
127
+ <div class="flex flex-col gap-2 w-full max-w-[280px] pointer-events-auto bg-black/50 p-3 rounded-xl border border-white/10">
128
+ <div class="flex justify-between items-center text-xs">
129
+ <span class="font-bold flex items-center gap-1 text-yellow-400">
130
+ <svg class="w-4 h-4 fill-current" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
131
+ YOUR HEALTH
132
+ </span>
133
+ <span id="hpValue" class="font-extrabold text-sm">200 / 200 HP</span>
134
+ </div>
135
+ <!-- HP Bar -->
136
+ <div class="w-full h-4 bg-gray-900 rounded-full overflow-hidden border border-gray-700 p-[2px]">
137
+ <div id="hpBar" class="h-full bg-gradient-to-r from-green-500 via-green-400 to-emerald-500 rounded-full transition-all duration-100" style="width: 100%"></div>
138
+ </div>
139
+ <div class="text-[10px] text-gray-400 font-mono mt-1" id="phaseIndicator">
140
+ Current Phase: Plane se koodiye
141
+ </div>
142
+ </div>
143
+
144
+ <!-- Right Corner: Active Gun Slot / Shoot Buttons -->
145
+ <div class="flex gap-4 items-end pointer-events-auto">
146
+ <!-- Active Gun Slot View -->
147
+ <div id="weaponSlot" class="bg-black/70 border border-yellow-500/40 p-3 rounded-2xl w-48 text-right flex flex-col gap-1 shadow-lg">
148
+ <span class="text-[10px] text-gray-400 uppercase tracking-widest block font-bold">Looted Gun</span>
149
+ <h3 id="gunName" class="text-xl font-black text-yellow-400">Khaali Hath (Fist)</h3>
150
+ <div class="flex justify-between items-center mt-1">
151
+ <span id="gunAmmo" class="text-xs font-mono text-gray-300">AMMO: -- / --</span>
152
+ <span id="gunTypeLabel" class="text-[9px] bg-yellow-500/20 text-yellow-400 px-2 py-0.5 rounded uppercase font-semibold">MELEE</span>
153
+ </div>
154
+ </div>
155
+
156
+ <!-- Touch controls for firing and jumping (only visible on touch) -->
157
+ <div id="touchActionButtons" class="flex flex-col gap-3">
158
+ <button id="mobileFireBtn" class="w-16 h-16 bg-red-600/80 active:bg-red-500 border-2 border-red-400 text-white rounded-full flex items-center justify-center font-bold text-lg shadow-xl shadow-red-900/30 touch-none select-none" style="display: none;">
159
+ 🔥
160
+ </button>
161
+ <button id="mobileJumpBtn" class="w-14 h-14 bg-gray-700/80 active:bg-gray-600 border-2 border-gray-500 text-white rounded-full flex items-center justify-center font-bold text-xs shadow-lg touch-none select-none" style="display: none;">
162
+ JUMP
163
+ </button>
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+
169
+ <!-- Joystick Area for Mobile -->
170
+ <div id="joystickContainer" class="joystick-container">
171
+ <div id="joystickKnob" class="joystick-knob"></div>
172
+ </div>
173
+
174
+ <!-- Game Over Screen Modal -->
175
+ <div id="gameOverScreen" class="absolute inset-0 bg-black/90 backdrop-blur-md flex flex-col justify-center items-center text-center p-6 text-white hidden z-50">
176
+ <h1 id="gameOverTitle" class="text-6xl font-black text-yellow-500 uppercase tracking-wider mb-2">BOOYAH!</h1>
177
+ <p id="gameOverSub" class="text-xl text-gray-300 mb-8">Aapne sabhi dushmano ko hara diya!</p>
178
+
179
+ <div class="bg-white/5 border border-white/10 rounded-2xl p-6 mb-8 w-full max-w-sm flex justify-around">
180
+ <div>
181
+ <span class="text-xs text-gray-400 block">Kills</span>
182
+ <span id="goKills" class="text-3xl font-black text-yellow-400">0</span>
183
+ </div>
184
+ <div class="border-r border-white/10"></div>
185
+ <div>
186
+ <span class="text-xs text-gray-400 block">Rank achieved</span>
187
+ <span id="goRank" class="text-3xl font-black text-orange-400">#1 / 100</span>
188
+ </div>
189
+ </div>
190
+
191
+ <button onclick="window.location.reload()" class="px-8 py-4 bg-yellow-500 hover:bg-yellow-400 text-black font-black rounded-xl text-lg uppercase tracking-wider shadow-lg shadow-yellow-500/30 transform active:scale-95 transition-all">
192
+ Fir Se Kheliye 🔄
193
+ </button>
194
+ </div>
195
+
196
+ <script>
197
+ // Check if device is touch based
198
+ const isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);
199
+ if (isTouchDevice) {
200
+ document.getElementById('joystickContainer').style.display = 'flex';
201
+ document.getElementById('mobileFireBtn').style.display = 'flex';
202
+ document.getElementById('mobileJumpBtn').style.display = 'flex';
203
+ }
204
+
205
+ // --- CONSTANTS & DATA DESIGNS ---
206
+
207
+ // 10 types of guns inside Free Fire
208
+ const GUNS = [
209
+ { id: 'fist', name: 'Fist (Khaali Hath)', type: 'Melee', damage: 15, range: 4, ammo: '∞', maxAmmo: '∞', color: 0x8c7853 },
210
+ { id: 'm1887', name: 'M1887 (Double Barrel)', type: 'Shotgun', damage: 75, range: 15, ammo: 2, maxAmmo: 2, reloadTime: 1.5, color: 0x424242 },
211
+ { id: 'mp40', name: 'MP40 (Sub-machine Gun)', type: 'SMG', damage: 18, range: 25, ammo: 30, maxAmmo: 30, reloadTime: 2.0, color: 0xe5c158 },
212
+ { id: 'ak47', name: 'AK47 (Assault Rifle)', type: 'AR', damage: 38, range: 45, ammo: 30, maxAmmo: 30, reloadTime: 2.5, color: 0xa15232 },
213
+ { id: 'awm', name: 'AWM (Sniper Rifle)', type: 'Sniper', damage: 150, range: 100, ammo: 5, maxAmmo: 5, reloadTime: 3.5, color: 0x43a047 },
214
+ { id: 'scar', name: 'SCAR-L (Stable AR)', type: 'AR', damage: 30, range: 40, ammo: 30, maxAmmo: 30, reloadTime: 2.2, color: 0xbcaaa4 },
215
+ { id: 'desert_eagle', name: 'Desert Eagle', type: 'Pistol', damage: 45, range: 20, ammo: 7, maxAmmo: 7, reloadTime: 1.8, color: 0xb0bec5 },
216
+ { id: 'm4a1', name: 'M4A1 (Classic AR)', type: 'AR', damage: 28, range: 42, ammo: 30, maxAmmo: 30, reloadTime: 2.3, color: 0x2e3b4e },
217
+ { id: 'mp5', name: 'MP5 (Fast SMG)', type: 'SMG', damage: 20, range: 28, ammo: 35, maxAmmo: 35, reloadTime: 1.9, color: 0x3e2723 },
218
+ { id: 'groza', name: 'Groza (Airdrop Weapon)', type: 'AR', damage: 42, range: 48, ammo: 30, maxAmmo: 30, reloadTime: 2.4, color: 0x4e342e }
219
+ ];
220
+
221
+ // Map dimensions
222
+ const MAP_SIZE = 1000; // Large 3D island terrain size
223
+
224
+ // --- GAME SYSTEM STATE ---
225
+ const state = {
226
+ phase: 'plane', // 'plane', 'skydiving', 'parachute', 'ground'
227
+ playersCount: 100,
228
+ kills: 0,
229
+ health: 200,
230
+ isGameOver: false,
231
+ rank: 100,
232
+
233
+ // Map Zone Info
234
+ zone: {
235
+ x: 0,
236
+ z: 0,
237
+ radius: 450,
238
+ targetRadius: 450,
239
+ shrinkTimer: 45, // Next phase starts in seconds
240
+ shrinkRate: 0.1,
241
+ isActive: false,
242
+ color: 0x0000ff
243
+ },
244
+
245
+ // Keyboard State
246
+ keys: { w: false, a: false, s: false, d: false, space: false, f: false },
247
+
248
+ // Touch JoyStick values
249
+ touch: {
250
+ active: false,
251
+ startX: 0,
252
+ startY: 0,
253
+ dx: 0,
254
+ dy: 0
255
+ },
256
+
257
+ // Weapon state
258
+ currentWeapon: GUNS[0], // Fist initially
259
+ lastFired: 0,
260
+ reloading: false
261
+ };
262
+
263
+ // --- SCENE & SETUP ---
264
+ const scene = new THREE.Scene();
265
+ scene.background = new THREE.Color(0xaaccff); // Sky blue color
266
+ scene.fog = new THREE.FogExp2(0xaaccff, 0.0015);
267
+
268
+ const camera = new THREE.PerspectiveCamera(65, window.innerWidth / window.innerHeight, 0.1, 2000);
269
+ const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('gameCanvas'), antialias: true });
270
+ renderer.setSize(window.innerWidth, window.innerHeight);
271
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
272
+ renderer.shadowMap.enabled = true;
273
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
274
+
275
+ // --- LIGHTING ---
276
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
277
+ scene.add(ambientLight);
278
+
279
+ const dirLight = new THREE.DirectionalLight(0xfff4d6, 1.2);
280
+ dirLight.position.set(300, 400, 100);
281
+ dirLight.castShadow = true;
282
+ dirLight.shadow.mapSize.width = 1024;
283
+ dirLight.shadow.mapSize.height = 1024;
284
+ dirLight.shadow.camera.near = 0.5;
285
+ dirLight.shadow.camera.far = 1500;
286
+ const d = 500;
287
+ dirLight.shadow.camera.left = -d;
288
+ dirLight.shadow.camera.right = d;
289
+ dirLight.shadow.camera.top = d;
290
+ dirLight.shadow.camera.bottom = -d;
291
+ scene.add(dirLight);
292
+
293
+ // --- TERRAIN CREATION ---
294
+ // Create an Island with mountains, trees, houses, and loot spawns.
295
+ const terrainGeom = new THREE.PlaneGeometry(MAP_SIZE, MAP_SIZE, 64, 64);
296
+ terrainGeom.rotateX(-Math.PI / 2);
297
+
298
+ // Displace vertices to create mountains, valleys and standard island profile
299
+ const pos = terrainGeom.attributes.position;
300
+ const heights = [];
301
+ for (let i = 0; i < pos.count; i++) {
302
+ const x = pos.getX(i);
303
+ const z = pos.getZ(i);
304
+
305
+ // Circular falloff for Island shapes
306
+ const distFromCenter = Math.sqrt(x*x + z*z);
307
+ let height = 0;
308
+
309
+ if (distFromCenter < MAP_SIZE / 2) {
310
+ // Natural hills noise using sin/cos functions
311
+ height = Math.sin(x * 0.02) * Math.cos(z * 0.02) * 12;
312
+ height += Math.sin(x * 0.005) * Math.sin(z * 0.005) * 25;
313
+ // Flat center landing fields
314
+ if (distFromCenter < 120) {
315
+ height = height * 0.3;
316
+ }
317
+ // High mountain in a corner
318
+ if (x > 150 && z > 150) {
319
+ height += 45;
320
+ }
321
+ // Ocean edge smooth falloff
322
+ const edgeFactor = (MAP_SIZE / 2 - distFromCenter) / (MAP_SIZE / 2);
323
+ height = height * Math.min(1, edgeFactor * 2.5);
324
+ } else {
325
+ height = -15; // Sea floor
326
+ }
327
+
328
+ pos.setY(i, height);
329
+ heights.push(height);
330
+ }
331
+ terrainGeom.computeVertexNormals();
332
+
333
+ // Terrain styling (realistic grass color)
334
+ const terrainMat = new THREE.MeshStandardMaterial({
335
+ color: 0x3d642d,
336
+ roughness: 0.9,
337
+ metalness: 0.1,
338
+ flatShading: true
339
+ });
340
+ const terrain = new THREE.Mesh(terrainGeom, terrainMat);
341
+ terrain.receiveShadow = true;
342
+ scene.add(terrain);
343
+
344
+ // Ocean floor / Water plane
345
+ const waterGeom = new THREE.PlaneGeometry(MAP_SIZE * 2, MAP_SIZE * 2);
346
+ const waterMat = new THREE.MeshStandardMaterial({
347
+ color: 0x1d4e89,
348
+ roughness: 0.2,
349
+ metalness: 0.8,
350
+ transparent: true,
351
+ opacity: 0.75
352
+ });
353
+ const water = new THREE.Mesh(waterGeom, waterMat);
354
+ water.rotateX(-Math.PI / 2);
355
+ water.position.y = -3; // Sea level height
356
+ scene.add(water);
357
+
358
+ // Helper function to get height of terrain at x, z
359
+ function getTerrainHeight(x, z) {
360
+ // Quick mathematical height estimate corresponding to our generator
361
+ const dist = Math.sqrt(x*x + z*z);
362
+ if (dist >= MAP_SIZE / 2) return -3;
363
+
364
+ let h = Math.sin(x * 0.02) * Math.cos(z * 0.02) * 12;
365
+ h += Math.sin(x * 0.005) * Math.sin(z * 0.005) * 25;
366
+ if (dist < 120) h *= 0.3;
367
+ if (x > 150 && z > 150) h += 45;
368
+
369
+ const edgeFactor = (MAP_SIZE / 2 - dist) / (MAP_SIZE / 2);
370
+ h = h * Math.min(1, edgeFactor * 2.5);
371
+ return h;
372
+ }
373
+
374
+ // --- ENVIRONMENT OBJECTS (Trees, Houses, Safe Zone Circle) ---
375
+
376
+ const trees = [];
377
+ const treeGeom = new THREE.ConeGeometry(3, 10, 5);
378
+ const treeTrunkGeom = new THREE.CylinderGeometry(0.5, 0.7, 4, 5);
379
+ const foliageMat = new THREE.MeshStandardMaterial({ color: 0x1c3d13, roughness: 0.9, flatShading: true });
380
+ const trunkMat = new THREE.MeshStandardMaterial({ color: 0x5a3825, roughness: 0.9 });
381
+
382
+ // Generate 350 random scattered trees
383
+ for (let i = 0; i < 350; i++) {
384
+ const rx = (Math.random() - 0.5) * (MAP_SIZE * 0.75);
385
+ const rz = (Math.random() - 0.5) * (MAP_SIZE * 0.75);
386
+ const th = getTerrainHeight(rx, rz);
387
+
388
+ if (th > 0) { // Trees only on solid ground
389
+ const group = new THREE.Group();
390
+ const foliage = new THREE.Mesh(treeGeom, foliageMat);
391
+ foliage.position.y = 5;
392
+ foliage.castShadow = true;
393
+
394
+ const trunk = new THREE.Mesh(treeTrunkGeom, trunkMat);
395
+ trunk.position.y = 2;
396
+ trunk.castShadow = true;
397
+
398
+ group.add(trunk);
399
+ group.add(foliage);
400
+ group.position.set(rx, th, rz);
401
+
402
+ const scale = 0.6 + Math.random() * 0.8;
403
+ group.scale.set(scale, scale, scale);
404
+
405
+ scene.add(group);
406
+ trees.push({ mesh: group, radius: 1.5 * scale });
407
+ }
408
+ }
409
+
410
+ // Houses / Buildings
411
+ const houses = [];
412
+ const houseWallMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.8 });
413
+ const houseRoofMat = new THREE.MeshStandardMaterial({ color: 0xb71c1c, roughness: 0.6 });
414
+
415
+ // Simple function to build a looting structure
416
+ function createHouse(x, z) {
417
+ const h = getTerrainHeight(x, z);
418
+ if (h < 0) return;
419
+
420
+ const house = new THREE.Group();
421
+
422
+ // Walls
423
+ const wGeom = new THREE.BoxGeometry(12, 8, 12);
424
+ const walls = new THREE.Mesh(wGeom, houseWallMat);
425
+ walls.position.y = 4;
426
+ walls.castShadow = true;
427
+ walls.receiveShadow = true;
428
+ house.add(walls);
429
+
430
+ // Roof (Pyramid style)
431
+ const rGeom = new THREE.ConeGeometry(10, 5, 4);
432
+ rGeom.rotateY(Math.PI / 4);
433
+ const roof = new THREE.Mesh(rGeom, houseRoofMat);
434
+ roof.position.y = 10.5;
435
+ roof.castShadow = true;
436
+ house.add(roof);
437
+
438
+ house.position.set(x, h, z);
439
+ scene.add(house);
440
+
441
+ houses.push({
442
+ x: x,
443
+ z: z,
444
+ size: 8, // AABB bounding cylinder size
445
+ height: 8
446
+ });
447
+ }
448
+
449
+ // Spawn 25 small houses across the map center areas
450
+ for (let i = 0; i < 25; i++) {
451
+ const angle = Math.random() * Math.PI * 2;
452
+ const dist = 50 + Math.random() * 320;
453
+ const hX = Math.cos(angle) * dist;
454
+ const hZ = Math.sin(angle) * dist;
455
+ createHouse(hX, hZ);
456
+ }
457
+
458
+ // --- LOOT / WEAPON SPAWNS ---
459
+ const weaponDrops = [];
460
+
461
+ // Render 3D Gun on ground
462
+ function spawnWeaponMesh(gunType, x, z) {
463
+ const h = getTerrainHeight(x, z) + 1;
464
+ const gunGroup = new THREE.Group();
465
+
466
+ // Main barrel shape
467
+ const barrelGeom = new THREE.BoxGeometry(0.3, 0.3, 2.5);
468
+ const barrelMat = new THREE.MeshStandardMaterial({ color: gunType.color, metalness: 0.8, roughness: 0.2 });
469
+ const barrel = new THREE.Mesh(barrelGeom, barrelMat);
470
+ gunGroup.add(barrel);
471
+
472
+ // Grip / Stock
473
+ const gripGeom = new THREE.BoxGeometry(0.25, 0.7, 0.5);
474
+ const grip = new THREE.Mesh(gripGeom, barrelMat);
475
+ grip.position.set(0, -0.4, -0.5);
476
+ gunGroup.add(grip);
477
+
478
+ // Floating Light Ring below the weapon
479
+ const ringGeom = new THREE.RingGeometry(1.5, 1.8, 16);
480
+ ringGeom.rotateX(-Math.PI/2);
481
+ const ringMat = new THREE.MeshBasicMaterial({ color: 0xffd700, side: THREE.DoubleSide, transparent: true, opacity: 0.7 });
482
+ const ring = new THREE.Mesh(ringGeom, ringMat);
483
+ ring.position.y = -0.9;
484
+ gunGroup.add(ring);
485
+
486
+ gunGroup.position.set(x, h, z);
487
+ scene.add(gunGroup);
488
+
489
+ weaponDrops.push({
490
+ mesh: gunGroup,
491
+ gun: gunType,
492
+ x: x,
493
+ z: z,
494
+ radius: 3
495
+ });
496
+ }
497
+
498
+ // Populate island with 100 gun pickups
499
+ for (let i = 0; i < 120; i++) {
500
+ const randomGun = GUNS[1 + Math.floor(Math.random() * (GUNS.length - 1))]; // Exclude Fist
501
+ const angle = Math.random() * Math.PI * 2;
502
+ const dist = Math.random() * 400;
503
+ const wx = Math.cos(angle) * dist;
504
+ const wz = Math.sin(angle) * dist;
505
+ if (getTerrainHeight(wx, wz) > 0) {
506
+ spawnWeaponMesh(randomGun, wx, wz);
507
+ }
508
+ }
509
+
510
+ // --- CHARACTER BUILDER (PLAYER & ENEMY BOTS) ---
511
+ // Generates realistic anatomically separate body structure
512
+ function createCharacterMesh(isPlayer = false) {
513
+ const charGroup = new THREE.Group();
514
+
515
+ // Materials
516
+ const skinColor = isPlayer ? 0xffdbac : 0xb58050;
517
+ const shirtColor = isPlayer ? 0xff3333 : 0x2244aa; // Red for player, Blue for Enemy Bots
518
+ const pantsColor = 0x333333;
519
+
520
+ const skinMat = new THREE.MeshStandardMaterial({ color: skinColor, roughness: 0.8 });
521
+ const shirtMat = new THREE.MeshStandardMaterial({ color: shirtColor, roughness: 0.8 });
522
+ const pantsMat = new THREE.MeshStandardMaterial({ color: pantsColor, roughness: 0.9 });
523
+
524
+ // Torso (Body)
525
+ const torsoGeom = new THREE.BoxGeometry(1.6, 2.4, 0.8);
526
+ const torso = new THREE.Mesh(torsoGeom, shirtMat);
527
+ torso.position.y = 3.6;
528
+ torso.castShadow = true;
529
+ charGroup.add(torso);
530
+
531
+ // Head (Sir)
532
+ const headGeom = new THREE.BoxGeometry(1.0, 1.0, 1.0);
533
+ const head = new THREE.Mesh(headGeom, skinMat);
534
+ head.position.y = 5.4;
535
+ head.castShadow = true;
536
+ charGroup.add(head);
537
+
538
+ // Hair/Cap for detail
539
+ const capGeom = new THREE.BoxGeometry(1.1, 0.3, 1.1);
540
+ const capMat = new THREE.MeshStandardMaterial({ color: isPlayer ? 0xffcc00 : 0x111111 });
541
+ const cap = new THREE.Mesh(capGeom, capMat);
542
+ cap.position.set(0, 5.9, 0.05);
543
+ charGroup.add(cap);
544
+
545
+ // Hands (2 Hath) - Rotational Pivots added
546
+ const handGeom = new THREE.BoxGeometry(0.5, 2.2, 0.5);
547
+
548
+ const leftHandPivot = new THREE.Group();
549
+ leftHandPivot.position.set(-1.1, 4.6, 0);
550
+ const leftHand = new THREE.Mesh(handGeom, shirtMat);
551
+ leftHand.position.y = -1.0;
552
+ leftHand.castShadow = true;
553
+ leftHandPivot.add(leftHand);
554
+ charGroup.add(leftHandPivot);
555
+
556
+ const rightHandPivot = new THREE.Group();
557
+ rightHandPivot.position.set(1.1, 4.6, 0);
558
+ const rightHand = new THREE.Mesh(handGeom, shirtMat);
559
+ rightHand.position.y = -1.0;
560
+ rightHand.castShadow = true;
561
+ rightHandPivot.add(rightHand);
562
+ charGroup.add(rightHandPivot);
563
+
564
+ // Legs (2 Per)
565
+ const legGeom = new THREE.BoxGeometry(0.6, 2.4, 0.6);
566
+
567
+ const leftLegPivot = new THREE.Group();
568
+ leftLegPivot.position.set(-0.45, 2.4, 0);
569
+ const leftLeg = new THREE.Mesh(legGeom, pantsMat);
570
+ leftLeg.position.y = -1.2;
571
+ leftLeg.castShadow = true;
572
+ leftLegPivot.add(leftLeg);
573
+ charGroup.add(leftLegPivot);
574
+
575
+ const rightLegPivot = new THREE.Group();
576
+ rightLegPivot.position.set(0.45, 2.4, 0);
577
+ const rightLeg = new THREE.Mesh(legGeom, pantsMat);
578
+ rightLeg.position.y = -1.2;
579
+ rightLeg.castShadow = true;
580
+ rightLegPivot.add(rightLeg);
581
+ charGroup.add(rightLegPivot);
582
+
583
+ // Gun model inside hand
584
+ const gunHandModel = new THREE.Group();
585
+ const gBarrelGeom = new THREE.BoxGeometry(0.2, 0.2, 1.5);
586
+ const gBarrelMat = new THREE.MeshBasicMaterial({ color: 0x111111 });
587
+ const gBarrel = new THREE.Mesh(gBarrelGeom, gBarrelMat);
588
+ gBarrel.position.set(0, -0.4, 0.7);
589
+ gunHandModel.add(gBarrel);
590
+ rightHandPivot.add(gunHandModel);
591
+ gunHandModel.visible = false; // Hidden unless holding gun
592
+
593
+ // Store parts references for walking animation loops
594
+ charGroup.userData = {
595
+ leftHand: leftHandPivot,
596
+ rightHand: rightHandPivot,
597
+ leftLeg: leftLegPivot,
598
+ rightLeg: rightLegPivot,
599
+ gun: gunHandModel
600
+ };
601
+
602
+ return charGroup;
603
+ }
604
+
605
+ // --- PLANE / SKYDIVE BALLOON MODELS ---
606
+
607
+ // Big Free Fire plane
608
+ const planeGroup = new THREE.Group();
609
+ const planeFuselage = new THREE.Mesh(new THREE.CylinderGeometry(8, 8, 50, 8), new THREE.MeshStandardMaterial({ color: 0xeeeeee }));
610
+ planeFuselage.rotateX(Math.PI / 2);
611
+ planeGroup.add(planeFuselage);
612
+
613
+ const planeWing = new THREE.Mesh(new THREE.BoxGeometry(70, 1, 12), new THREE.MeshStandardMaterial({ color: 0xbbbbbb }));
614
+ planeWing.position.set(0, 0, 5);
615
+ planeGroup.add(planeWing);
616
+
617
+ planeGroup.position.set(-500, 280, -100);
618
+ scene.add(planeGroup);
619
+
620
+ // Parachute Balloon (Initially invisible, attached to player later)
621
+ const balloonGroup = new THREE.Group();
622
+ const balloonGeom = new THREE.SphereGeometry(6, 16, 16);
623
+ const balloonMat = new THREE.MeshStandardMaterial({ color: 0xff5500, roughness: 0.3 });
624
+ const balloonMesh = new THREE.Mesh(balloonGeom, balloonMat);
625
+ balloonMesh.scale.set(1.4, 0.8, 1.4); // Flat circular balloon look
626
+ balloonMesh.position.y = 12;
627
+ balloonGroup.add(balloonMesh);
628
+
629
+ // Parachute ropes
630
+ const ropeMat = new THREE.LineBasicMaterial({ color: 0xffffff });
631
+ for (let angle = 0; angle < Math.PI * 2; angle += Math.PI / 2) {
632
+ const rx = Math.cos(angle) * 5;
633
+ const rz = Math.sin(angle) * 5;
634
+ const points = [
635
+ new THREE.Vector3(0, 4, 0),
636
+ new THREE.Vector3(rx, 10, rz)
637
+ ];
638
+ const ropeGeom = new THREE.BufferGeometry().setFromPoints(points);
639
+ const line = new THREE.Line(ropeGeom, ropeMat);
640
+ balloonGroup.add(line);
641
+ }
642
+ scene.add(balloonGroup);
643
+ balloonGroup.visible = false;
644
+
645
+ // --- PLAYER SETUP ---
646
+ const playerMesh = createCharacterMesh(true);
647
+ playerMesh.position.set(0, 0, 0); // Spawns initially inactive
648
+ scene.add(playerMesh);
649
+
650
+ const player = {
651
+ mesh: playerMesh,
652
+ x: 0,
653
+ y: 350,
654
+ z: 0,
655
+ vx: 0,
656
+ vy: 0,
657
+ vz: 0,
658
+ rotY: 0,
659
+ rotX: 0, // pitch for camera look
660
+ health: 200,
661
+ maxHealth: 200,
662
+ isGrounded: false,
663
+ inventory: [GUNS[0]], // starts with Fist
664
+ currentWeaponIdx: 0,
665
+ kills: 0,
666
+ alive: true,
667
+ walkCycle: 0
668
+ };
669
+
670
+ // --- ENEMY BOTS SPAWNING ---
671
+ // 99 Bots spawned inside Island area
672
+ const bots = [];
673
+ const botNames = ["Dynamo", "Ajjubhai", "GyanGaming", "LokeshG", "TotalGaming", "RaiStar", "DesiGamer", "Badge99", "Amitbhai", "CarryMinati", "Ninja", "Shroud", "Gosu", "Scythe", "Ghost", "Specter", "Viper", "Tornado", "Bullet", "SniperGod", "Kronten", "Mortal", "Scout", "Regaltos", "ViperS", "Thunder", "Shadow", "Alpha", "Omega", "Raptor", "Slayer", "Beast", "Doom", "Apex", "Zero", "Nova", "Rex", "Savage", "Venom", "Toxic", "Phoenix", "Titan", "Rogue", "Glitch", "Phantom", "Reaper", "Spectre", "Hunter", "Warrior", "Knight", "Spartan", "Viking", "Samurai", "NinjaX", "Assasin", "Hitman", "Agent47", "Trigger", "Reload", "Headshot", "OneTap", "AimBot", "Recoil", "Scope", "Muzzle", "Silencer", "Airdrop", "LootBox", "BooyahKing", "ChickenDinner", "Victory", "Champion", "Legend", "Mythic", "Hero", "Grandmaster", "Bronze", "Silver", "Gold", "Platinum", "Diamond", "Heroic", "ProPlayer", "NoobSlayer", "Bot01", "Bot02", "Bot03", "Bot04", "Bot05", "Bot06", "Bot07", "Bot08", "Bot09", "Bot10", "Bot11", "Bot12", "Bot13", "Bot14", "Bot15"];
674
+
675
+ function createBot(id) {
676
+ const botMesh = createCharacterMesh(false);
677
+
678
+ // Scatter bots completely around island inside 400 radius
679
+ const angle = Math.random() * Math.PI * 2;
680
+ const dist = 60 + Math.random() * 320;
681
+ const bx = Math.cos(angle) * dist;
682
+ const bz = Math.sin(angle) * dist;
683
+ const by = getTerrainHeight(bx, bz);
684
+
685
+ botMesh.position.set(bx, by, bz);
686
+ scene.add(botMesh);
687
+
688
+ // Assign a random weapon to the bot
689
+ const randomWeapon = GUNS[Math.floor(Math.random() * GUNS.length)];
690
+ if (randomWeapon.id !== 'fist') {
691
+ botMesh.userData.gun.visible = true;
692
+ }
693
+
694
+ bots.push({
695
+ id: id,
696
+ name: botNames[id % botNames.length] || `Survivor_${id}`,
697
+ mesh: botMesh,
698
+ x: bx,
699
+ y: by,
700
+ z: bz,
701
+ health: 100,
702
+ weapon: randomWeapon,
703
+ state: 'wandering', // 'wandering', 'chasing', 'shooting'
704
+ targetX: bx,
705
+ targetZ: bz,
706
+ actionTimer: Math.random() * 3,
707
+ alive: true,
708
+ walkCycle: 0,
709
+ shootTimer: 0
710
+ });
711
+ }
712
+
713
+ for (let i = 0; i < 99; i++) {
714
+ createBot(i);
715
+ }
716
+
717
+ // --- SPARK & PARTICLE SYSTEM (Realistic Shoot Spark) ---
718
+ const sparkParticles = [];
719
+ const sparkGeom = new THREE.BufferGeometry();
720
+ const maxSparks = 200;
721
+ const sparkPositions = new Float32Array(maxSparks * 3);
722
+ const sparkColors = new Float32Array(maxSparks * 3);
723
+
724
+ // Init blank sparks positions
725
+ for (let i = 0; i < maxSparks * 3; i++) {
726
+ sparkPositions[i] = 0;
727
+ sparkColors[i] = 1;
728
+ }
729
+ sparkGeom.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
730
+ const sparkMat = new THREE.PointsMaterial({
731
+ size: 0.8,
732
+ vertexColors: true,
733
+ transparent: true,
734
+ blending: THREE.AdditiveBlending,
735
+ depthWrite: false
736
+ });
737
+ const sparkSystem = new THREE.Points(sparkGeom, sparkMat);
738
+ scene.add(sparkSystem);
739
+
740
+ function triggerShootSpark(origin, direction) {
741
+ // Create particles moving rapidly outward
742
+ for (let i = 0; i < 15; i++) {
743
+ const particle = {
744
+ x: origin.x + (Math.random() - 0.5) * 0.5,
745
+ y: origin.y + (Math.random() - 0.5) * 0.5,
746
+ z: origin.z + (Math.random() - 0.5) * 0.5,
747
+ vx: (direction.x + (Math.random() - 0.5) * 0.4) * 8,
748
+ vy: (direction.y + (Math.random() - 0.5) * 0.4) * 8,
749
+ vz: (direction.z + (Math.random() - 0.5) * 0.4) * 8,
750
+ life: 1.0,
751
+ decay: 0.05 + Math.random() * 0.08
752
+ };
753
+ sparkParticles.push(particle);
754
+ if (sparkParticles.length > maxSparks) {
755
+ sparkParticles.shift();
756
+ }
757
+ }
758
+ }
759
+
760
+ function updateSparks() {
761
+ const posAttr = sparkGeom.attributes.position;
762
+ for (let i = 0; i < maxSparks; i++) {
763
+ const part = sparkParticles[i];
764
+ if (part && part.life > 0) {
765
+ part.x += part.vx * 0.05;
766
+ part.y += part.vy * 0.05;
767
+ part.z += part.vz * 0.05;
768
+ part.life -= part.decay;
769
+
770
+ posAttr.setXYZ(i, part.x, part.y, part.z);
771
+ } else {
772
+ posAttr.setXYZ(i, 9999, 9999, 9999); // offscreen
773
+ }
774
+ }
775
+ posAttr.needsUpdate = true;
776
+ }
777
+
778
+ // --- SAFE ZONE DANGER DOME (FF Dome visual effect) ---
779
+ const domeGeom = new THREE.CylinderGeometry(1, 1, 500, 32, 1, true);
780
+ const domeMat = new THREE.MeshBasicMaterial({
781
+ color: 0x00a8ff,
782
+ transparent: true,
783
+ opacity: 0.15,
784
+ side: THREE.DoubleSide,
785
+ wireframe: true
786
+ });
787
+ const dangerDome = new THREE.Mesh(domeGeom, domeMat);
788
+ scene.add(dangerDome);
789
+
790
+ // --- CONTROLS LISTENERS ---
791
+
792
+ // Keyboard Handlers
793
+ window.addEventListener('keydown', (e) => {
794
+ const k = e.key.toLowerCase();
795
+ if (k === 'w' || e.key === 'ArrowUp') state.keys.w = true;
796
+ if (k === 'a' || e.key === 'ArrowLeft') state.keys.a = true;
797
+ if (k === 's' || e.key === 'ArrowDown') state.keys.s = true;
798
+ if (k === 'd' || e.key === 'ArrowRight') state.keys.d = true;
799
+ if (e.key === ' ') state.keys.space = true;
800
+ if (k === 'f') state.keys.f = true;
801
+ });
802
+
803
+ window.addEventListener('keyup', (e) => {
804
+ const k = e.key.toLowerCase();
805
+ if (k === 'w' || e.key === 'ArrowUp') state.keys.w = false;
806
+ if (k === 'a' || e.key === 'ArrowLeft') state.keys.a = false;
807
+ if (k === 's' || e.key === 'ArrowDown') state.keys.s = false;
808
+ if (k === 'd' || e.key === 'ArrowRight') state.keys.d = false;
809
+ if (e.key === ' ') state.keys.space = false;
810
+ if (k === 'f') state.keys.f = false;
811
+ });
812
+
813
+ // Mouse Drag to look around
814
+ let isPointerLocked = false;
815
+ const canvas = renderer.domElement;
816
+
817
+ // Pointer Lock request for PC FPS Mode
818
+ canvas.addEventListener('click', () => {
819
+ if (state.phase === 'ground' && !isTouchDevice) {
820
+ canvas.requestPointerLock();
821
+ }
822
+ });
823
+
824
+ document.addEventListener('pointerlockchange', () => {
825
+ isPointerLocked = (document.pointerLockElement === canvas);
826
+ });
827
+
828
+ window.addEventListener('mousemove', (e) => {
829
+ if (isPointerLocked) {
830
+ player.rotY -= e.movementX * 0.0025;
831
+ player.rotX -= e.movementY * 0.0025;
832
+ player.rotX = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, player.rotX));
833
+ } else if (isTouchDevice && state.touch.active === false) {
834
+ // If touch drag rotating view
835
+ // handled by direct touchmove on window below
836
+ }
837
+ });
838
+
839
+ // Mobile Drag look rotation & Joystick
840
+ let touchStartRotX = 0;
841
+ let touchStartRotY = 0;
842
+ let rotationTouchId = null;
843
+
844
+ window.addEventListener('touchstart', (e) => {
845
+ for (let i = 0; i < e.touches.length; i++) {
846
+ const touch = e.touches[i];
847
+ if (touch.clientX < window.innerWidth / 2) {
848
+ // Left half: Virtual joystick
849
+ state.touch.active = true;
850
+ state.touch.startX = touch.clientX;
851
+ state.touch.startY = touch.clientY;
852
+ document.getElementById('joystickContainer').style.left = `${touch.clientX - 60}px`;
853
+ document.getElementById('joystickContainer').style.bottom = `${window.innerHeight - touch.clientY - 60}px`;
854
+ } else {
855
+ // Right half: Touch look rotation
856
+ rotationTouchId = touch.identifier;
857
+ touchStartRotY = player.rotY;
858
+ touchStartRotX = player.rotX;
859
+ }
860
+ }
861
+ });
862
+
863
+ window.addEventListener('touchmove', (e) => {
864
+ for (let i = 0; i < e.touches.length; i++) {
865
+ const touch = e.touches[i];
866
+ if (state.touch.active && touch.clientX < window.innerWidth / 2) {
867
+ const dx = touch.clientX - state.touch.startX;
868
+ const dy = touch.clientY - state.touch.startY;
869
+ const len = Math.sqrt(dx*dx + dy*dy);
870
+ const maxLen = 50;
871
+
872
+ if (len > 0) {
873
+ const nx = dx / len;
874
+ const ny = dy / len;
875
+ const finalLen = Math.min(len, maxLen);
876
+
877
+ state.touch.dx = nx * (finalLen / maxLen);
878
+ state.touch.dy = ny * (finalLen / maxLen);
879
+
880
+ document.getElementById('joystickKnob').style.transform = `translate(${state.touch.dx * 30}px, ${state.touch.dy * 30}px)`;
881
+ }
882
+ } else if (touch.identifier === rotationTouchId) {
883
+ const deltaX = touch.clientX - (window.innerWidth * 0.75); // rough screen center displacement
884
+ player.rotY -= (deltaX * 0.0001);
885
+ }
886
+ }
887
+ });
888
+
889
+ window.addEventListener('touchend', (e) => {
890
+ let activeJoystickTouch = false;
891
+ for (let i = 0; i < e.touches.length; i++) {
892
+ if (e.touches[i].clientX < window.innerWidth / 2) activeJoystickTouch = true;
893
+ }
894
+ if (!activeJoystickTouch) {
895
+ state.touch.active = false;
896
+ state.touch.dx = 0;
897
+ state.touch.dy = 0;
898
+ document.getElementById('joystickContainer').style.left = '30px';
899
+ document.getElementById('joystickContainer').style.bottom = '30px';
900
+ document.getElementById('joystickKnob').style.transform = 'translate(0px, 0px)';
901
+ }
902
+ });
903
+
904
+ // --- ACTION BUTTON TRIGGERS ---
905
+ const actionBtn = document.getElementById('actionBtn');
906
+ actionBtn.addEventListener('click', () => {
907
+ if (state.phase === 'plane') {
908
+ // Plane se jump karein
909
+ state.phase = 'skydiving';
910
+ player.x = planeGroup.position.x;
911
+ player.z = planeGroup.position.z;
912
+ player.y = 280;
913
+ player.vy = -3.5; // High gravity skydive speed
914
+
915
+ actionBtn.innerText = '🎈 PARACHUTE DEPLOY KAREIN';
916
+ document.getElementById('screenTitle').innerText = 'SKYDIVING...';
917
+ document.getElementById('screenSubtitle').innerText = 'Zameen ke paas aakar Parachute kholein.';
918
+ document.getElementById('phaseIndicator').innerText = 'Current Phase: Skydiving (Hava me hain)';
919
+ } else if (state.phase === 'skydiving') {
920
+ // Parachute deploy karein
921
+ state.phase = 'parachute';
922
+ player.vy = -0.8; // Low terminal velocity with parachute
923
+ balloonGroup.visible = true;
924
+
925
+ actionBtn.style.display = 'none';
926
+ document.getElementById('screenTitle').innerText = 'LANDING SAFE...';
927
+ document.getElementById('screenSubtitle').innerText = 'Zameen par dhyan se utrein.';
928
+ document.getElementById('phaseIndicator').innerText = 'Current Phase: Parachuting (Landing)';
929
+ }
930
+ });
931
+
932
+ // Fire triggers
933
+ window.addEventListener('mousedown', () => {
934
+ if (state.phase === 'ground') shootPlayerWeapon();
935
+ });
936
+
937
+ document.getElementById('mobileFireBtn').addEventListener('touchstart', (e) => {
938
+ e.preventDefault();
939
+ if (state.phase === 'ground') shootPlayerWeapon();
940
+ });
941
+
942
+ document.getElementById('mobileJumpBtn').addEventListener('touchstart', (e) => {
943
+ e.preventDefault();
944
+ if (player.isGrounded && state.phase === 'ground') {
945
+ player.vy = 1.6;
946
+ player.isGrounded = false;
947
+ }
948
+ });
949
+
950
+ // --- COLLISION UTILS ---
951
+ function checkAABBCollision(obj1, obj2) {
952
+ const dist = Math.sqrt((obj1.x - obj2.x)**2 + (obj1.z - obj2.z)**2);
953
+ return dist < (obj1.radius + obj2.radius);
954
+ }
955
+
956
+ // --- WEAPON SHOOT LOGIC ---
957
+ function shootPlayerWeapon() {
958
+ const now = performance.now();
959
+ const w = state.currentWeapon;
960
+
961
+ if (w.ammo !== '∞' && w.ammo <= 0) {
962
+ addKillFeed("Ammo khatam! Doosri gun dhoondho.");
963
+ return;
964
+ }
965
+
966
+ if (now - state.lastFired < (w.reloadTime ? w.reloadTime * 200 : 300)) return;
967
+ state.lastFired = now;
968
+
969
+ if (w.ammo !== '∞') {
970
+ w.ammo--;
971
+ document.getElementById('gunAmmo').innerText = `AMMO: ${w.ammo} / ${w.maxAmmo}`;
972
+ }
973
+
974
+ // Muzzle flash sound / visual
975
+ const cameraDir = new THREE.Vector3();
976
+ camera.getWorldDirection(cameraDir);
977
+
978
+ // Origin position right hand
979
+ const shootOrigin = player.mesh.position.clone().add(new THREE.Vector3(0, 4, 0));
980
+ triggerShootSpark(shootOrigin, cameraDir);
981
+
982
+ // Raycaster for hit detection
983
+ const raycaster = new THREE.Raycaster(shootOrigin, cameraDir, 0, w.range * 4);
984
+
985
+ // Check collision with all alive bots
986
+ const hits = [];
987
+ bots.forEach(bot => {
988
+ if (!bot.alive) return;
989
+ // Simplified spherical bounding hit test for accuracy
990
+ const distToRay = raycaster.ray.distanceToPoint(bot.mesh.position);
991
+ const distanceAlongRay = raycaster.ray.direction.clone().dot(bot.mesh.position.clone().sub(shootOrigin));
992
+
993
+ if (distToRay < 3.5 && distanceAlongRay > 0 && distanceAlongRay < w.range * 4) {
994
+ hits.push({ bot: bot, dist: distanceAlongRay });
995
+ }
996
+ });
997
+
998
+ if (hits.length > 0) {
999
+ // Hit closest target
1000
+ hits.sort((a,b) => a.dist - b.dist);
1001
+ const targetBot = hits[0].bot;
1002
+
1003
+ targetBot.health -= w.damage;
1004
+ triggerShootSpark(targetBot.mesh.position, cameraDir.multiplyScalar(-0.5)); // Splat spark
1005
+
1006
+ if (targetBot.health <= 0) {
1007
+ targetBot.alive = false;
1008
+ targetBot.mesh.visible = false;
1009
+ state.kills++;
1010
+ state.playersCount--;
1011
+
1012
+ document.getElementById('killCounter').innerText = state.kills;
1013
+ document.getElementById('aliveCounter').innerText = state.playersCount;
1014
+
1015
+ addKillFeed(`🔴 Aapne ${targetBot.name} ko maar diya!`);
1016
+
1017
+ // Drop weapon on death spot
1018
+ if (targetBot.weapon.id !== 'fist') {
1019
+ spawnWeaponMesh(targetBot.weapon, targetBot.x, targetBot.z);
1020
+ }
1021
+ } else {
1022
+ addKillFeed(`💥 Hit: -${w.damage} HP to ${targetBot.name}`);
1023
+ }
1024
+ }
1025
+ }
1026
+
1027
+ // --- NOTIFICATION SYSTEM (Kill Feed) ---
1028
+ function addKillFeed(msg) {
1029
+ const feed = document.getElementById('killFeed');
1030
+ const alertDiv = document.createElement('div');
1031
+ alertDiv.className = "bg-red-900/80 text-white border-l-4 border-red-500 text-xs px-3 py-1.5 rounded shadow-lg font-bold transition-all animate-bounce";
1032
+ alertDiv.innerText = msg;
1033
+
1034
+ feed.appendChild(alertDiv);
1035
+ if (feed.children.length > 4) {
1036
+ feed.removeChild(feed.children[0]);
1037
+ }
1038
+ setTimeout(() => {
1039
+ if (alertDiv.parentNode) alertDiv.remove();
1040
+ }, 5000);
1041
+ }
1042
+
1043
+ // --- MINIMAP GRAPHICS CANVAS ---
1044
+ const mapCanvas = document.getElementById('minimapCanvas');
1045
+ const mapCtx = mapCanvas.getContext('2d');
1046
+
1047
+ function drawMinimap() {
1048
+ mapCtx.fillStyle = '#224411'; // Forest floor
1049
+ mapCtx.fillRect(0, 0, 112, 112);
1050
+
1051
+ // Scale factor to map real coordinates [-500, 500] to [0, 112]
1052
+ const scale = 112 / MAP_SIZE;
1053
+ const toCanvas = (val) => (val + MAP_SIZE/2) * scale;
1054
+
1055
+ // Safe Zone Circle representation
1056
+ mapCtx.strokeStyle = '#00a8ff';
1057
+ mapCtx.lineWidth = 2;
1058
+ mapCtx.beginPath();
1059
+ mapCtx.arc(toCanvas(state.zone.x), toCanvas(state.zone.z), state.zone.radius * scale, 0, Math.PI * 2);
1060
+ mapCtx.stroke();
1061
+
1062
+ // Draw player dot
1063
+ mapCtx.fillStyle = '#ffff00'; // Yellow dot for player
1064
+ mapCtx.beginPath();
1065
+ mapCtx.arc(toCanvas(player.x), toCanvas(player.z), 4, 0, Math.PI * 2);
1066
+ mapCtx.fill();
1067
+
1068
+ // Draw nearby bots (Red dots)
1069
+ mapCtx.fillStyle = '#ff3300';
1070
+ bots.forEach(bot => {
1071
+ if (bot.alive) {
1072
+ const distToPlayer = Math.sqrt((bot.x - player.x)**2 + (bot.z - player.z)**2);
1073
+ if (distToPlayer < 200) { // Render only visible radar bots
1074
+ mapCtx.beginPath();
1075
+ mapCtx.arc(toCanvas(bot.x), toCanvas(bot.z), 2, 0, Math.PI * 2);
1076
+ mapCtx.fill();
1077
+ }
1078
+ }
1079
+ });
1080
+ }
1081
+
1082
+ // --- SIMULATED BOT ACTIONS ---
1083
+ // Let bots move, gather weapons, fight each other in background
1084
+ function updateBots(delta) {
1085
+ bots.forEach(bot => {
1086
+ if (!bot.alive) return;
1087
+
1088
+ bot.actionTimer -= delta;
1089
+ if (bot.actionTimer <= 0) {
1090
+ bot.actionTimer = 2 + Math.random() * 4;
1091
+ // Decide next state
1092
+ const r = Math.random();
1093
+ if (r < 0.6) {
1094
+ bot.state = 'wandering';
1095
+ // Pick random coordinates near safe zone
1096
+ bot.targetX = state.zone.x + (Math.random() - 0.5) * state.zone.radius;
1097
+ bot.targetZ = state.zone.z + (Math.random() - 0.5) * state.zone.radius;
1098
+ } else {
1099
+ // Look for closest target
1100
+ let closest = null;
1101
+ let minDist = 250;
1102
+
1103
+ // Check player distance
1104
+ const distToPlayer = Math.sqrt((player.x - bot.x)**2 + (player.z - bot.z)**2);
1105
+ if (distToPlayer < minDist && state.phase === 'ground' && player.alive) {
1106
+ closest = player;
1107
+ minDist = distToPlayer;
1108
+ }
1109
+
1110
+ // Check other bots distance
1111
+ bots.forEach(other => {
1112
+ if (other.id !== bot.id && other.alive) {
1113
+ const d = Math.sqrt((other.x - bot.x)**2 + (other.z - bot.z)**2);
1114
+ if (d < minDist) {
1115
+ closest = other;
1116
+ minDist = d;
1117
+ }
1118
+ }
1119
+ });
1120
+
1121
+ if (closest) {
1122
+ bot.state = 'chasing';
1123
+ bot.targetX = closest.mesh.position.x;
1124
+ bot.targetZ = closest.mesh.position.z;
1125
+
1126
+ // If very close, start firing!
1127
+ if (minDist < bot.weapon.range * 4) {
1128
+ bot.state = 'shooting';
1129
+ }
1130
+ }
1131
+ }
1132
+ }
1133
+
1134
+ // Execute States
1135
+ if (bot.state === 'wandering' || bot.state === 'chasing') {
1136
+ // Walk towards destination
1137
+ const dx = bot.targetX - bot.x;
1138
+ const dz = bot.targetZ - bot.z;
1139
+ const dist = Math.sqrt(dx*dx + dz*dz);
1140
+
1141
+ if (dist > 3) {
1142
+ const speed = 15 * delta; // move speed
1143
+ bot.x += (dx / dist) * speed;
1144
+ bot.z += (dz / dist) * speed;
1145
+ bot.y = getTerrainHeight(bot.x, bot.z);
1146
+
1147
+ // Rotation look
1148
+ const angle = Math.atan2(dx, dz);
1149
+ bot.mesh.rotation.y = angle;
1150
+
1151
+ // Limb Animate
1152
+ bot.walkCycle += delta * 12;
1153
+ const legsAmp = Math.sin(bot.walkCycle) * 0.6;
1154
+ bot.mesh.userData.leftLeg.rotation.x = legsAmp;
1155
+ bot.mesh.userData.rightLeg.rotation.x = -legsAmp;
1156
+ bot.mesh.userData.leftHand.rotation.x = -legsAmp;
1157
+ bot.mesh.userData.rightHand.rotation.x = legsAmp;
1158
+ }
1159
+ } else if (bot.state === 'shooting') {
1160
+ // Stand and shoot target
1161
+ bot.shootTimer -= delta;
1162
+ if (bot.shootTimer <= 0) {
1163
+ bot.shootTimer = 1.0 + Math.random() * 1.5;
1164
+
1165
+ // Shoot simulation visuals
1166
+ const fireDir = new THREE.Vector3(
1167
+ (bot.targetX - bot.x),
1168
+ 0,
1169
+ (bot.targetZ - bot.z)
1170
+ ).normalize();
1171
+
1172
+ triggerShootSpark(bot.mesh.position.clone().add(new THREE.Vector3(0,4,0)), fireDir);
1173
+
1174
+ // Random damage simulation
1175
+ const hitChance = Math.random();
1176
+ if (hitChance > 0.6) {
1177
+ // Hit player
1178
+ const dist = Math.sqrt((player.x - bot.x)**2 + (player.z - bot.z)**2);
1179
+ if (dist < bot.weapon.range * 4 && player.alive && state.phase === 'ground') {
1180
+ state.health -= bot.weapon.damage * 0.5; // lower bot damage for fairness
1181
+ addKillFeed(`⚠️ ${bot.name} ne aapko damage diya (-${Math.floor(bot.weapon.damage * 0.5)} HP)!`);
1182
+ updateHPBar();
1183
+
1184
+ if (state.health <= 0) {
1185
+ triggerGameOver(false, bot.name);
1186
+ }
1187
+ }
1188
+ } else {
1189
+ // Simulated Bots killing other bots background
1190
+ bots.forEach(other => {
1191
+ if (other.id !== bot.id && other.alive) {
1192
+ const d = Math.sqrt((other.x - bot.x)**2 + (other.z - bot.z)**2);
1193
+ if (d < bot.weapon.range * 4 && Math.random() > 0.8) {
1194
+ other.health -= bot.weapon.damage;
1195
+ if (other.health <= 0) {
1196
+ other.alive = false;
1197
+ other.mesh.visible = false;
1198
+ state.playersCount--;
1199
+ document.getElementById('aliveCounter').innerText = state.playersCount;
1200
+ addKillFeed(`💀 ${bot.name} ne ${other.name} ko maar diya`);
1201
+ }
1202
+ }
1203
+ }
1204
+ });
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ bot.mesh.position.set(bot.x, bot.y, bot.z);
1210
+ });
1211
+ }
1212
+
1213
+ // --- HEALTH DAMAGE & REPAIR ---
1214
+ function updateHPBar() {
1215
+ const bar = document.getElementById('hpBar');
1216
+ const val = document.getElementById('hpValue');
1217
+ const percentage = Math.max(0, (state.health / state.maxHealth) * 100);
1218
+
1219
+ bar.style.width = `${percentage}%`;
1220
+ val.innerText = `${Math.ceil(state.health)} / 200 HP`;
1221
+
1222
+ if (percentage < 30) {
1223
+ bar.className = "h-full bg-gradient-to-r from-red-600 to-red-500 rounded-full transition-all";
1224
+ } else {
1225
+ bar.className = "h-full bg-gradient-to-r from-green-500 via-green-400 to-emerald-500 rounded-full transition-all";
1226
+ }
1227
+ }
1228
+
1229
+ function triggerGameOver(isBooyah, killerName = "Danger Zone") {
1230
+ state.isGameOver = true;
1231
+ document.getElementById('gameOverScreen').classList.remove('hidden');
1232
+
1233
+ const title = document.getElementById('gameOverTitle');
1234
+ const sub = document.getElementById('gameOverSub');
1235
+ const killsDisp = document.getElementById('goKills');
1236
+ const rankDisp = document.getElementById('goRank');
1237
+
1238
+ killsDisp.innerText = state.kills;
1239
+ rankDisp.innerText = `#${state.playersCount + 1} / 100`;
1240
+
1241
+ if (isBooyah) {
1242
+ title.innerText = "🏆 BOOYAH! 🏆";
1243
+ title.className = "text-6xl font-black text-green-500 uppercase tracking-wider mb-2 animate-pulse";
1244
+ sub.innerText = "Aapne is match me vijay prapt ki!";
1245
+ } else {
1246
+ title.innerText = "ELIMINATED";
1247
+ title.className = "text-6xl font-black text-red-600 uppercase tracking-wider mb-2";
1248
+ sub.innerText = `Aapko ${killerName} ne hara diya. Behtar koshish agli baar!`;
1249
+ }
1250
+ }
1251
+
1252
+ // --- GAME MAIN LOOPS ---
1253
+ let lastTime = performance.now();
1254
+
1255
+ function gameLoop() {
1256
+ if (state.isGameOver) return;
1257
+
1258
+ requestAnimationFrame(gameLoop);
1259
+
1260
+ const now = performance.now();
1261
+ const delta = (now - lastTime) / 1000;
1262
+ lastTime = now;
1263
+
1264
+ // --- 1. PLANE PHASE ANIMATION ---
1265
+ if (state.phase === 'plane') {
1266
+ planeGroup.position.x += 40 * delta;
1267
+ if (planeGroup.position.x > MAP_SIZE / 2 + 100) {
1268
+ planeGroup.position.x = -MAP_SIZE / 2 - 100; // loop plane
1269
+ }
1270
+
1271
+ // Camera tracks airplane
1272
+ camera.position.set(planeGroup.position.x, planeGroup.position.y + 40, planeGroup.position.z + 80);
1273
+ camera.lookAt(planeGroup.position);
1274
+ }
1275
+
1276
+ // --- 2. SKYDIVING PHASE ---
1277
+ else if (state.phase === 'skydiving') {
1278
+ player.y += player.vy * 10 * delta;
1279
+
1280
+ // Fall mechanics controls (W, A, S, D steer falling direction)
1281
+ if (state.keys.w || state.touch.dy < 0) { player.x += 18 * delta; }
1282
+ if (state.keys.s || state.touch.dy > 0) { player.x -= 18 * delta; }
1283
+ if (state.keys.a || state.touch.dx < 0) { player.z -= 18 * delta; }
1284
+ if (state.keys.d || state.touch.dx > 0) { player.z += 18 * delta; }
1285
+
1286
+ playerMesh.position.set(player.x, player.y, player.z);
1287
+
1288
+ // Rotations during skydive
1289
+ playerMesh.rotation.z = Math.sin(now * 0.01) * 0.2;
1290
+
1291
+ // Follow Camera
1292
+ camera.position.set(player.x, player.y + 25, player.z + 40);
1293
+ camera.lookAt(playerMesh.position);
1294
+
1295
+ // Auto-deploy parachute if too low
1296
+ if (player.y < 80) {
1297
+ actionBtn.click();
1298
+ }
1299
+ }
1300
+
1301
+ // --- 3. PARACHUTING PHASE ---
1302
+ else if (state.phase === 'parachute') {
1303
+ player.y += player.vy * 10 * delta;
1304
+
1305
+ // Wind drift
1306
+ if (state.keys.w || state.touch.dy < 0) { player.x += 8 * delta; }
1307
+ if (state.keys.s || state.touch.dy > 0) { player.x -= 8 * delta; }
1308
+ if (state.keys.a || state.touch.dx < 0) { player.z -= 8 * delta; }
1309
+ if (state.keys.d || state.touch.dx > 0) { player.z += 8 * delta; }
1310
+
1311
+ playerMesh.position.set(player.x, player.y, player.z);
1312
+
1313
+ // Attach parachute/balloon model
1314
+ balloonGroup.visible = true;
1315
+ balloonGroup.position.set(player.x, player.y, player.z);
1316
+
1317
+ // Camera angles downwards
1318
+ camera.position.set(player.x, player.y + 18, player.z + 30);
1319
+ camera.lookAt(playerMesh.position);
1320
+
1321
+ // Detect Ground landing
1322
+ const landH = getTerrainHeight(player.x, player.z);
1323
+ if (player.y <= landH + 1) {
1324
+ state.phase = 'ground';
1325
+ player.y = landH;
1326
+ player.vy = 0;
1327
+ player.isGrounded = true;
1328
+ balloonGroup.visible = false;
1329
+ document.getElementById('crosshair').style.display = 'block';
1330
+ document.getElementById('screenTitle').style.display = 'none';
1331
+ document.getElementById('screenSubtitle').style.display = 'none';
1332
+ document.getElementById('phaseIndicator').innerText = 'Phase: ON ISLAND Ground Combat (Loot and Shoot)';
1333
+ }
1334
+ }
1335
+
1336
+ // --- 4. GROUND COMBAT CONTROLS (FF Real Gameplay) ---
1337
+ else if (state.phase === 'ground') {
1338
+ // Movement vector
1339
+ const moveVec = new THREE.Vector3(0, 0, 0);
1340
+
1341
+ // Keyboard steer
1342
+ if (state.keys.w) moveVec.z -= 1;
1343
+ if (state.keys.s) moveVec.z += 1;
1344
+ if (state.keys.a) moveVec.x -= 1;
1345
+ if (state.keys.d) moveVec.x += 1;
1346
+
1347
+ // Touch steer
1348
+ if (state.touch.active) {
1349
+ moveVec.x = state.touch.dx;
1350
+ moveVec.z = state.touch.dy;
1351
+ }
1352
+
1353
+ moveVec.normalize();
1354
+
1355
+ // Rotate steer matching camera rotation direction
1356
+ moveVec.applyAxisAngle(new THREE.Vector3(0, 1, 0), player.rotY);
1357
+
1358
+ const speed = 25 * delta; // ground run speed
1359
+ player.x += moveVec.x * speed;
1360
+ player.z += moveVec.z * speed;
1361
+
1362
+ // Gravity & Jump Physics
1363
+ if (!player.isGrounded) {
1364
+ player.vy -= 4.2 * delta;
1365
+ player.y += player.vy;
1366
+
1367
+ const groundY = getTerrainHeight(player.x, player.z);
1368
+ if (player.y <= groundY) {
1369
+ player.y = groundY;
1370
+ player.vy = 0;
1371
+ player.isGrounded = true;
1372
+ }
1373
+ } else {
1374
+ player.y = getTerrainHeight(player.x, player.z);
1375
+ if (state.keys.space) {
1376
+ player.vy = 1.6;
1377
+ player.isGrounded = false;
1378
+ }
1379
+ }
1380
+
1381
+ // Apply values to Player mesh position
1382
+ playerMesh.position.set(player.x, player.y, player.z);
1383
+ playerMesh.rotation.y = player.rotY;
1384
+
1385
+ // Move Legs/Hands loop when running
1386
+ if (moveVec.lengthSq() > 0.01) {
1387
+ player.walkCycle += delta * 15;
1388
+ const legsAmp = Math.sin(player.walkCycle) * 0.7;
1389
+ playerMesh.userData.leftLeg.rotation.x = legsAmp;
1390
+ playerMesh.userData.rightLeg.rotation.x = -legsAmp;
1391
+ playerMesh.userData.leftHand.rotation.x = -legsAmp;
1392
+ playerMesh.userData.rightHand.rotation.x = legsAmp;
1393
+ } else {
1394
+ // Reset to idle pose
1395
+ playerMesh.userData.leftLeg.rotation.x = 0;
1396
+ playerMesh.userData.rightLeg.rotation.x = 0;
1397
+ playerMesh.userData.leftHand.rotation.x = 0;
1398
+ playerMesh.userData.rightHand.rotation.x = 0;
1399
+ }
1400
+
1401
+ // 3rd Person POV Camera Position
1402
+ const cameraDistance = 14;
1403
+ const cameraHeight = 6.5;
1404
+
1405
+ const camX = player.x - Math.sin(player.rotY) * cameraDistance;
1406
+ const camZ = player.z - Math.cos(player.rotY) * cameraDistance;
1407
+ const camY = player.y + cameraHeight + (Math.sin(player.rotX) * 5);
1408
+
1409
+ camera.position.set(camX, camY, camZ);
1410
+ camera.lookAt(player.x, player.y + 4, player.z);
1411
+
1412
+ // --- COLLISION LOOT COLLECTION ---
1413
+ weaponDrops.forEach((pickup, idx) => {
1414
+ if (pickup.mesh.visible) {
1415
+ const distToPlayer = Math.sqrt((pickup.x - player.x)**2 + (pickup.z - player.z)**2);
1416
+ if (distToPlayer < pickup.radius) {
1417
+ pickup.mesh.visible = false;
1418
+
1419
+ // Loot current item
1420
+ state.currentWeapon = pickup.gun;
1421
+ playerMesh.userData.gun.visible = true; // show in hand
1422
+
1423
+ document.getElementById('gunName').innerText = pickup.gun.name;
1424
+ document.getElementById('gunAmmo').innerText = `AMMO: ${pickup.gun.ammo} / ${pickup.gun.maxAmmo}`;
1425
+ document.getElementById('gunTypeLabel').innerText = pickup.gun.type;
1426
+
1427
+ addKillFeed(`🔫 Aapne ${pickup.gun.name} loot kar li!`);
1428
+ }
1429
+ }
1430
+ });
1431
+
1432
+ // Update Safe Zone timers
1433
+ updateSafeZone(delta);
1434
+ updateSparks();
1435
+
1436
+ // Check Victory Match State
1437
+ if (state.playersCount <= 1 && player.alive) {
1438
+ triggerGameOver(true);
1439
+ }
1440
+ }
1441
+
1442
+ // --- 5. BOTS ENGINE RUN ---
1443
+ updateBots(delta);
1444
+
1445
+ // Redraw HUD Minimap
1446
+ drawMinimap();
1447
+
1448
+ renderer.render(scene, camera);
1449
+ }
1450
+
1451
+ // --- SAFE ZONE MECHANICS (Danger Shrinking Zone) ---
1452
+ function updateSafeZone(delta) {
1453
+ state.zone.shrinkTimer -= delta;
1454
+
1455
+ if (state.zone.shrinkTimer <= 0) {
1456
+ // Zone starts contracting
1457
+ state.zone.isActive = true;
1458
+ state.zone.targetRadius = Math.max(50, state.zone.radius - 80);
1459
+ state.zone.shrinkTimer = 50; // next contract trigger
1460
+
1461
+ document.getElementById('dangerAlert').classList.remove('hidden');
1462
+ setTimeout(() => {
1463
+ document.getElementById('dangerAlert').classList.add('hidden');
1464
+ }, 4000);
1465
+ }
1466
+
1467
+ // Shrink Zone animation slowly
1468
+ if (state.zone.isActive && state.zone.radius > state.zone.targetRadius) {
1469
+ state.zone.radius -= 3 * delta;
1470
+ if (state.zone.radius <= state.zone.targetRadius) {
1471
+ state.zone.isActive = false;
1472
+ }
1473
+ }
1474
+
1475
+ document.getElementById('zoneTimerText').innerText = `Next Safe Zone: ${Math.ceil(state.zone.shrinkTimer)}s`;
1476
+
1477
+ // Adjust outer blue visual dome size
1478
+ dangerDome.position.set(state.zone.x, 0, state.zone.z);
1479
+ dangerDome.scale.set(state.zone.radius, 1, state.zone.radius);
1480
+
1481
+ // Check if player is outside of safe circle zone (Danger damage)
1482
+ const dist = Math.sqrt((player.x - state.zone.x)**2 + (player.z - state.zone.z)**2);
1483
+ if (dist > state.zone.radius && state.phase === 'ground') {
1484
+ state.health -= 5 * delta; // continuous poison zone damage
1485
+ updateHPBar();
1486
+
1487
+ if (state.health <= 0) {
1488
+ triggerGameOver(false, "Danger Zone");
1489
+ }
1490
+ }
1491
+ }
1492
+
1493
+ // --- START GAME LOOP ---
1494
+ window.onload = function() {
1495
+ gameLoop();
1496
+ };
1497
+
1498
+ // Window resize responsive
1499
+ window.addEventListener('resize', () => {
1500
+ camera.aspect = window.innerWidth / window.innerHeight;
1501
+ camera.updateProjectionMatrix();
1502
+ renderer.setSize(window.innerWidth, window.innerHeight);
1503
+ });
1504
+
1505
+ </script>
1506
+ </body>
1507
+ </html>