Update index.html
Browse files- index.html +1507 -19
index.html
CHANGED
|
@@ -1,19 +1,1507 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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>
|