hytopia 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/boilerplate/assets/map.json +191 -43
  2. package/docs/server.playercameramode.md +14 -0
  3. package/examples/hygrounds/README.md +0 -0
  4. package/examples/hygrounds/assets/audio/sfx/chest-open-1.mp3 +0 -0
  5. package/examples/hygrounds/assets/audio/sfx/chest-open-2.mp3 +0 -0
  6. package/examples/hygrounds/assets/audio/sfx/machine-gun-reload.mp3 +0 -0
  7. package/examples/hygrounds/assets/audio/sfx/machine-gun-shoot.mp3 +0 -0
  8. package/examples/hygrounds/assets/audio/sfx/medpack-consume.mp3 +0 -0
  9. package/examples/hygrounds/assets/audio/sfx/mining-drill-drilling.mp3 +0 -0
  10. package/examples/hygrounds/assets/audio/sfx/pistol-reload.mp3 +0 -0
  11. package/examples/hygrounds/assets/audio/sfx/pistol-shoot.mp3 +0 -0
  12. package/examples/hygrounds/assets/audio/sfx/player-hurt.mp3 +0 -0
  13. package/examples/hygrounds/assets/audio/sfx/rifle-reload.mp3 +0 -0
  14. package/examples/hygrounds/assets/audio/sfx/rifle-shoot.mp3 +0 -0
  15. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-explosion.mp3 +0 -0
  16. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-reload.mp3 +0 -0
  17. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-shoot.mp3 +0 -0
  18. package/examples/hygrounds/assets/audio/sfx/shield-potion-consume.mp3 +0 -0
  19. package/examples/hygrounds/assets/audio/sfx/shield.png +0 -0
  20. package/examples/hygrounds/assets/audio/sfx/shotgun-reload.mp3 +0 -0
  21. package/examples/hygrounds/assets/audio/sfx/shotgun-shoot.mp3 +0 -0
  22. package/examples/hygrounds/assets/audio/sfx/sniper-reload.mp3 +0 -0
  23. package/examples/hygrounds/assets/audio/sfx/sniper-shoot.mp3 +0 -0
  24. package/examples/hygrounds/assets/icons/ak-47.png +0 -0
  25. package/examples/hygrounds/assets/icons/ammo.png +0 -0
  26. package/examples/hygrounds/assets/icons/auto-shotgun.png +0 -0
  27. package/examples/hygrounds/assets/icons/block.png +0 -0
  28. package/examples/hygrounds/assets/icons/bolt-action-sniper.png +0 -0
  29. package/examples/hygrounds/assets/icons/crown-bronze.png +0 -0
  30. package/examples/hygrounds/assets/icons/crown-gold.png +0 -0
  31. package/examples/hygrounds/assets/icons/crown-silver.png +0 -0
  32. package/examples/hygrounds/assets/icons/heart.png +0 -0
  33. package/examples/hygrounds/assets/icons/light-machine-gun.png +0 -0
  34. package/examples/hygrounds/assets/icons/medpack.png +0 -0
  35. package/examples/hygrounds/assets/icons/mining-drill.png +0 -0
  36. package/examples/hygrounds/assets/icons/pickaxe.png +0 -0
  37. package/examples/hygrounds/assets/icons/pistol.png +0 -0
  38. package/examples/hygrounds/assets/icons/rocket-launcher.png +0 -0
  39. package/examples/hygrounds/assets/icons/shield-potion.png +0 -0
  40. package/examples/hygrounds/assets/icons/shield.png +0 -0
  41. package/examples/hygrounds/assets/icons/shotgun.png +0 -0
  42. package/examples/hygrounds/assets/map.json +31796 -0
  43. package/examples/hygrounds/assets/models/environment/.optimized/chest/baseColor_1.png +0 -0
  44. package/examples/hygrounds/assets/models/environment/.optimized/chest/baseColor_2.png +0 -0
  45. package/examples/hygrounds/assets/models/environment/.optimized/chest/chest-named-nodes.bin +0 -0
  46. package/examples/hygrounds/assets/models/environment/.optimized/chest/chest-named-nodes.gltf +5386 -0
  47. package/examples/hygrounds/assets/models/environment/.optimized/chest/chest.bin +0 -0
  48. package/examples/hygrounds/assets/models/environment/.optimized/chest/chest.gltf +2825 -0
  49. package/examples/hygrounds/assets/models/environment/.optimized/chest/chest.gltf.md5 +1 -0
  50. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion-named-nodes.glb +0 -0
  51. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb +0 -0
  52. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb.md5 +1 -0
  53. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2-named-nodes.glb +0 -0
  54. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb +0 -0
  55. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb.md5 +1 -0
  56. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3-named-nodes.glb +0 -0
  57. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb +0 -0
  58. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb.md5 +1 -0
  59. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter-named-nodes.glb +0 -0
  60. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb +0 -0
  61. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb.md5 +1 -0
  62. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/baseColor_1.png +0 -0
  63. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/baseColor_2.png +0 -0
  64. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/muzzle-flash-named-nodes.bin +0 -0
  65. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/muzzle-flash-named-nodes.gltf +196 -0
  66. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/muzzle-flash.bin +0 -0
  67. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/muzzle-flash.gltf +225 -0
  68. package/examples/hygrounds/assets/models/environment/.optimized/muzzle-flash/muzzle-flash.gltf.md5 +1 -0
  69. package/examples/hygrounds/assets/models/environment/chest.gltf +1 -0
  70. package/examples/hygrounds/assets/models/environment/explosion.glb +0 -0
  71. package/examples/hygrounds/assets/models/environment/muzzle-flash.gltf +1 -0
  72. package/examples/hygrounds/assets/models/items/.optimized/ak-47/ak-47-named-nodes.glb +0 -0
  73. package/examples/hygrounds/assets/models/items/.optimized/ak-47/ak-47.glb +0 -0
  74. package/examples/hygrounds/assets/models/items/.optimized/ak-47/ak-47.glb.md5 +1 -0
  75. package/examples/hygrounds/assets/models/items/.optimized/auto-shotgun/auto-shotgun-named-nodes.glb +0 -0
  76. package/examples/hygrounds/assets/models/items/.optimized/auto-shotgun/auto-shotgun.glb +0 -0
  77. package/examples/hygrounds/assets/models/items/.optimized/auto-shotgun/auto-shotgun.glb.md5 +1 -0
  78. package/examples/hygrounds/assets/models/items/.optimized/bolt-action-sniper/bolt-action-sniper-named-nodes.glb +0 -0
  79. package/examples/hygrounds/assets/models/items/.optimized/bolt-action-sniper/bolt-action-sniper.glb +0 -0
  80. package/examples/hygrounds/assets/models/items/.optimized/bolt-action-sniper/bolt-action-sniper.glb.md5 +1 -0
  81. package/examples/hygrounds/assets/models/items/.optimized/light-machine-gun/light-machine-gun-named-nodes.glb +0 -0
  82. package/examples/hygrounds/assets/models/items/.optimized/light-machine-gun/light-machine-gun.glb +0 -0
  83. package/examples/hygrounds/assets/models/items/.optimized/light-machine-gun/light-machine-gun.glb.md5 +1 -0
  84. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit-named-nodes.glb +0 -0
  85. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb +0 -0
  86. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb.md5 +1 -0
  87. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack-named-nodes.glb +0 -0
  88. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb +0 -0
  89. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb.md5 +1 -0
  90. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill-named-nodes.glb +0 -0
  91. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb +0 -0
  92. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb.md5 +1 -0
  93. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/baseColor.png +0 -0
  94. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/pickaxe-named-nodes.bin +0 -0
  95. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/pickaxe-named-nodes.gltf +901 -0
  96. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/pickaxe.bin +0 -0
  97. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/pickaxe.gltf +131 -0
  98. package/examples/hygrounds/assets/models/items/.optimized/pickaxe/pickaxe.gltf.md5 +1 -0
  99. package/examples/hygrounds/assets/models/items/.optimized/pistol/pistol-named-nodes.glb +0 -0
  100. package/examples/hygrounds/assets/models/items/.optimized/pistol/pistol.glb +0 -0
  101. package/examples/hygrounds/assets/models/items/.optimized/pistol/pistol.glb.md5 +1 -0
  102. package/examples/hygrounds/assets/models/items/.optimized/rocket-launcher/rocket-launcher-named-nodes.glb +0 -0
  103. package/examples/hygrounds/assets/models/items/.optimized/rocket-launcher/rocket-launcher.glb +0 -0
  104. package/examples/hygrounds/assets/models/items/.optimized/rocket-launcher/rocket-launcher.glb.md5 +1 -0
  105. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile-named-nodes.glb +0 -0
  106. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb +0 -0
  107. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb.md5 +1 -0
  108. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion-named-nodes.glb +0 -0
  109. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb +0 -0
  110. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb.md5 +1 -0
  111. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2-named-nodes.glb +0 -0
  112. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb +0 -0
  113. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb.md5 +1 -0
  114. package/examples/hygrounds/assets/models/items/.optimized/shotgun/shotgun-named-nodes.glb +0 -0
  115. package/examples/hygrounds/assets/models/items/.optimized/shotgun/shotgun.glb +0 -0
  116. package/examples/hygrounds/assets/models/items/.optimized/shotgun/shotgun.glb.md5 +1 -0
  117. package/examples/hygrounds/assets/models/items/ak-47.glb +0 -0
  118. package/examples/hygrounds/assets/models/items/auto-shotgun.glb +0 -0
  119. package/examples/hygrounds/assets/models/items/bolt-action-sniper.glb +0 -0
  120. package/examples/hygrounds/assets/models/items/light-machine-gun.glb +0 -0
  121. package/examples/hygrounds/assets/models/items/medpack.glb +0 -0
  122. package/examples/hygrounds/assets/models/items/mining-drill.glb +0 -0
  123. package/examples/hygrounds/assets/models/items/pickaxe.gltf +1 -0
  124. package/examples/hygrounds/assets/models/items/pistol.glb +0 -0
  125. package/examples/hygrounds/assets/models/items/rocket-launcher.glb +0 -0
  126. package/examples/hygrounds/assets/models/items/rocket-missile.glb +0 -0
  127. package/examples/hygrounds/assets/models/items/shield-potion.glb +0 -0
  128. package/examples/hygrounds/assets/models/items/shotgun.glb +0 -0
  129. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/baseColor_1.png +0 -0
  130. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/baseColor_2.png +0 -0
  131. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/baseColor_3.png +0 -0
  132. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/baseColor_4.png +0 -0
  133. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/soldier-player-named-nodes.bin +0 -0
  134. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/soldier-player-named-nodes.gltf +7679 -0
  135. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/soldier-player.bin +0 -0
  136. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/soldier-player.gltf +7774 -0
  137. package/examples/hygrounds/assets/models/players/.optimized/soldier-player/soldier-player.gltf.md5 +1 -0
  138. package/examples/hygrounds/assets/models/players/soldier-player.gltf +1 -0
  139. package/examples/hygrounds/assets/ui/images/scope.png +0 -0
  140. package/examples/hygrounds/assets/ui/index.html +1072 -0
  141. package/examples/hygrounds/bun.lock +503 -0
  142. package/examples/hygrounds/classes/ChestEntity.ts +133 -0
  143. package/examples/hygrounds/classes/GameManager.ts +384 -0
  144. package/examples/hygrounds/classes/GamePlayerEntity.ts +564 -0
  145. package/examples/hygrounds/classes/GunEntity.ts +263 -0
  146. package/examples/hygrounds/classes/ItemEntity.ts +225 -0
  147. package/examples/hygrounds/classes/ItemFactory.ts +49 -0
  148. package/examples/hygrounds/classes/MeleeWeaponEntity.ts +138 -0
  149. package/examples/hygrounds/classes/TerrainDamageManager.ts +56 -0
  150. package/examples/hygrounds/classes/items/MedPackEntity.ts +43 -0
  151. package/examples/hygrounds/classes/items/ShieldPotionEntity.ts +43 -0
  152. package/examples/hygrounds/classes/weapons/AK47Entity.ts +43 -0
  153. package/examples/hygrounds/classes/weapons/AutoShotgunEntity.ts +80 -0
  154. package/examples/hygrounds/classes/weapons/BoltActionSniperEntity.ts +46 -0
  155. package/examples/hygrounds/classes/weapons/LightMachineGunEntity.ts +43 -0
  156. package/examples/hygrounds/classes/weapons/MiningDrillEntity.ts +38 -0
  157. package/examples/hygrounds/classes/weapons/PickaxeEntity.ts +38 -0
  158. package/examples/hygrounds/classes/weapons/PistolEntity.ts +46 -0
  159. package/examples/hygrounds/classes/weapons/RocketLauncherEntity.ts +186 -0
  160. package/examples/hygrounds/classes/weapons/ShotgunEntity.ts +84 -0
  161. package/examples/hygrounds/gameConfig.ts +398 -0
  162. package/examples/hygrounds/index.ts +40 -0
  163. package/examples/hygrounds/package.json +16 -0
  164. package/package.json +1 -1
  165. package/server.api.json +21 -0
  166. package/server.d.ts +3 -1
  167. package/server.js +116 -116
@@ -0,0 +1,1072 @@
1
+ <!-- Fonts-->
2
+ <link rel="preconnect" href="https://fonts.googleapis.com">
3
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
4
+ <link href="https://fonts.googleapis.com/css2?family=Inter&display=swap" rel="stylesheet">
5
+
6
+ <!-- UI-->
7
+ <div class="crosshair"></div>
8
+
9
+ <div class="damage-indicator-container">
10
+ <div class="damage-indicator top"></div>
11
+ <div class="damage-indicator right"></div>
12
+ <div class="damage-indicator bottom"></div>
13
+ <div class="damage-indicator left"></div>
14
+ </div>
15
+
16
+ <div class="hit-damage-container"></div>
17
+
18
+ <img src="{{CDN_ASSETS_URL}}/ui/images/scope.png" class="scope-overlay">
19
+
20
+ <!-- Game Start Announcement -->
21
+ <div class="game-start-announcement">DEATHMATCH!</div>
22
+
23
+ <!-- Leaderboard -->
24
+ <div class="leaderboard">
25
+ <div class="leaderboard-title">HyGrounds Deathmatch</div>
26
+ <div class="leaderboard-timer">0:00</div>
27
+ <div class="leaderboard-players-count">Players: 0</div>
28
+ <div class="leaderboard-header">
29
+ <div class="header-name">Player</div>
30
+ <div class="header-kills">Kills</div>
31
+ </div>
32
+ <div class="leaderboard-players">
33
+ <div class="leaderboard-player no-kills">
34
+ <div class="player-name">Waiting for players...</div>
35
+ <div class="player-kills"></div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="hud">
41
+ <div class="info-container">
42
+ <div class="ammo-indicator" style="display: none;">
43
+ <img src="{{CDN_ASSETS_URL}}/icons/ammo.png" alt="Ammo Icon" class="ammo-icon">
44
+ <span class="current-ammo">30</span>
45
+ <span class="ammo-divider">/</span>
46
+ <span class="total-ammo">30</span>
47
+ </div>
48
+ <div class="shield-bar">
49
+ <div class="shield-bar-fill"></div>
50
+ <img src="{{CDN_ASSETS_URL}}/icons/shield.png" alt="Shield Icon" class="shield-icon">
51
+ <div class="shield-text">0</div>
52
+ </div>
53
+ <div class="health-bar">
54
+ <div class="health-bar-fill"></div>
55
+ <img src="{{CDN_ASSETS_URL}}/icons/heart.png" alt="Health Icon" class="health-icon">
56
+ <div class="health-text">100</div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+
61
+ <div class="materials-counter">
62
+ <img src="{{CDN_ASSETS_URL}}/icons/block.png" alt="Materials Icon" class="materials-icon">
63
+ <span class="materials-amount">0</span>
64
+ </div>
65
+
66
+ <div class="inventory-hud">
67
+ <div class="inventory-slot inventory-active-slot" data-slot="0">
68
+ <div class="slot-number">F</div>
69
+ <img class="slot-icon" style="display: none;">
70
+ <div class="slot-quantity" style="display: none;"></div>
71
+ <div class="slot-name" style="display: none;"></div>
72
+ </div>
73
+
74
+ <div class="inventory-slot" data-slot="1">
75
+ <div class="slot-number">1</div>
76
+ <img class="slot-icon" style="display: none;">
77
+ <div class="slot-quantity" style="display: none;"></div>
78
+ <div class="slot-name" style="display: none;"></div>
79
+ </div>
80
+
81
+ <div class="inventory-slot" data-slot="2">
82
+ <div class="slot-number">2</div>
83
+ <img class="slot-icon" style="display: none;">
84
+ <div class="slot-quantity" style="display: none;"></div>
85
+ <div class="slot-name" style="display: none;"></div>
86
+ </div>
87
+
88
+ <div class="inventory-slot" data-slot="3">
89
+ <div class="slot-number">3</div>
90
+ <img class="slot-icon" style="display: none;">
91
+ <div class="slot-quantity" style="display: none;"></div>
92
+ <div class="slot-name" style="display: none;"></div>
93
+ </div>
94
+
95
+ <div class="inventory-slot" data-slot="4">
96
+ <div class="slot-number">4</div>
97
+ <img class="slot-icon" style="display: none;">
98
+ <div class="slot-quantity" style="display: none;"></div>
99
+ <div class="slot-name" style="display: none;"></div>
100
+ </div>
101
+
102
+ <div class="inventory-slot" data-slot="5">
103
+ <div class="slot-number">5</div>
104
+ <img class="slot-icon" style="display: none;">
105
+ <div class="slot-quantity" style="display: none;"></div>
106
+ <div class="slot-name" style="display: none;"></div>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Scene UI Templates -->
111
+ <template id="item-label-template">
112
+ <div class="item-label">
113
+ <div class="label-quantity"></div>
114
+ <div class="label-name"></div>
115
+ <div class="label-prompt">"E" to Pickup</div>
116
+ <div class="label-caret"></div>
117
+ </div>
118
+ </template>
119
+
120
+ <template id="chest-label-template">
121
+ <div class="item-label">
122
+ <div class="label-name"></div>
123
+ <div class="label-prompt">"E" to Open</div>
124
+ <div class="label-caret"></div>
125
+ </div>
126
+ </template>
127
+
128
+ <!-- UI Scripts-->
129
+ <script>
130
+ const CDN_ASSETS_URL = '{{CDN_ASSETS_URL}}';
131
+ let leaderboardKillCounts = {};
132
+ let gameEndTime = 0;
133
+ let timerInterval;
134
+
135
+ function updateLeaderboard() {
136
+ const leaderboardPlayers = document.querySelector('.leaderboard-players');
137
+ leaderboardPlayers.innerHTML = '';
138
+
139
+ // Check if game has started
140
+ if (gameEndTime === 0) {
141
+ // Show "Waiting for players..." message if game hasn't started
142
+ const waitingElement = document.createElement('div');
143
+ waitingElement.className = 'leaderboard-player no-kills';
144
+ waitingElement.innerHTML = `
145
+ <div class="player-name">Waiting for players...</div>
146
+ <div class="player-kills"></div>
147
+ `;
148
+ leaderboardPlayers.appendChild(waitingElement);
149
+ return;
150
+ }
151
+
152
+ // Get sorted players by kill count
153
+ const sortedPlayers = Object.entries(leaderboardKillCounts)
154
+ .sort((a, b) => b[1] - a[1]);
155
+
156
+ if (sortedPlayers.length === 0 || sortedPlayers.every(player => player[1] === 0)) {
157
+ // Show "No kills yet" message if no players have kills
158
+ const noKillsElement = document.createElement('div');
159
+ noKillsElement.className = 'leaderboard-player no-kills';
160
+ noKillsElement.innerHTML = `
161
+ <div class="player-name">No kills yet</div>
162
+ <div class="player-kills"></div>
163
+ `;
164
+ leaderboardPlayers.appendChild(noKillsElement);
165
+ return;
166
+ }
167
+
168
+ // Create player elements for the leaderboard
169
+ sortedPlayers.forEach((player, index) => {
170
+ const [username, killCount] = player;
171
+
172
+ // Skip players with 0 kills
173
+ if (killCount === 0) return;
174
+
175
+ const playerElement = document.createElement('div');
176
+ playerElement.className = 'leaderboard-player';
177
+
178
+ let rankIcon = '';
179
+ if (index === 0) {
180
+ rankIcon = `<img src="${CDN_ASSETS_URL}/icons/crown-gold.png" class="rank-icon">`;
181
+ } else if (index === 1) {
182
+ rankIcon = `<img src="${CDN_ASSETS_URL}/icons/crown-silver.png" class="rank-icon">`;
183
+ } else if (index === 2) {
184
+ rankIcon = `<img src="${CDN_ASSETS_URL}/icons/crown-bronze.png" class="rank-icon">`;
185
+ }
186
+
187
+ playerElement.innerHTML = `
188
+ <div class="player-name">
189
+ ${rankIcon}
190
+ ${username}
191
+ </div>
192
+ <div class="player-kills">${killCount}</div>
193
+ `;
194
+
195
+ leaderboardPlayers.appendChild(playerElement);
196
+ });
197
+ }
198
+
199
+ function updateTimer() {
200
+ const now = Date.now();
201
+ const timeRemaining = Math.max(0, gameEndTime - now);
202
+
203
+ // Format time as mm:ss
204
+ const minutes = Math.floor(timeRemaining / 60000);
205
+ const seconds = Math.floor((timeRemaining % 60000) / 1000);
206
+ const formattedTime = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
207
+
208
+ const timerElement = document.querySelector('.leaderboard-timer');
209
+ timerElement.textContent = formattedTime;
210
+
211
+ // Stop the timer when it reaches zero
212
+ if (timeRemaining <= 0) {
213
+ clearInterval(timerInterval);
214
+ }
215
+ }
216
+
217
+ function showGameStartAnnouncement() {
218
+ const announcement = document.querySelector('.game-start-announcement');
219
+ announcement.classList.add('active');
220
+
221
+ // Remove the active class after animation completes
222
+ setTimeout(() => {
223
+ announcement.classList.remove('active');
224
+ }, 3000);
225
+ }
226
+
227
+ hytopia.registerSceneUITemplate('item-label', (id, onState) => {
228
+ const template = document.getElementById('item-label-template');
229
+ const clone = template.content.cloneNode(true);
230
+ const labelName = clone.querySelector('.label-name');
231
+ const labelQuantity = clone.querySelector('.label-quantity');
232
+
233
+ onState(state => {
234
+ if (state.name) {
235
+ labelName.textContent = state.name;
236
+ }
237
+
238
+ if (state.quantity !== undefined) {
239
+ labelQuantity.textContent = state.quantity !== -1 ? state.quantity : '∞';
240
+ }
241
+ });
242
+
243
+ return clone;
244
+ });
245
+
246
+ hytopia.registerSceneUITemplate('chest-label', (id, onState) => {
247
+ const template = document.getElementById('chest-label-template');
248
+ const clone = template.content.cloneNode(true);
249
+ const labelName = clone.querySelector('.label-name');
250
+
251
+ onState(state => {
252
+ if (state.name) {
253
+ labelName.textContent = state.name;
254
+ }
255
+ });
256
+
257
+ return clone;
258
+ });
259
+
260
+ hytopia.onData(data => {
261
+ const { type } = data;
262
+
263
+ if (!type) {
264
+ return console.warn('No type received for data', data);
265
+ }
266
+
267
+ if (type === 'game-start') {
268
+ showGameStartAnnouncement();
269
+ }
270
+
271
+ if (type === 'ammo-indicator') {
272
+ const { ammo, totalAmmo, show, reloading } = data;
273
+
274
+ if (show !== undefined) {
275
+ const ammoIndicator = document.querySelector('.ammo-indicator');
276
+ ammoIndicator.style.display = show ? 'block' : 'none';
277
+ }
278
+
279
+ if (reloading !== undefined) {
280
+ const currentAmmo = document.querySelector('.current-ammo');
281
+ currentAmmo.textContent = reloading ? '...' : ammo;
282
+ }
283
+
284
+ if (ammo !== undefined) {
285
+ const currentAmmo = document.querySelector('.current-ammo');
286
+ currentAmmo.textContent = ammo;
287
+ }
288
+
289
+ if (totalAmmo !== undefined) {
290
+ const totalAmmoElement = document.querySelector('.total-ammo');
291
+ totalAmmoElement.textContent = totalAmmo;
292
+ }
293
+ }
294
+
295
+ if (type === 'damage-indicator') {
296
+ const { direction } = data;
297
+
298
+ // Make sure we have a valid direction object
299
+ if (!direction || typeof direction.x === 'undefined' || typeof direction.z === 'undefined') {
300
+ console.error('Invalid direction object:', direction);
301
+ return;
302
+ }
303
+
304
+ // Hide all indicators first
305
+ document.querySelectorAll('.damage-indicator').forEach(el => {
306
+ el.classList.remove('active');
307
+ });
308
+
309
+ // Determine which indicator to show based on the dominant direction
310
+ const absX = Math.abs(direction.x);
311
+ const absZ = Math.abs(direction.z);
312
+
313
+ let indicatorClass;
314
+ if (absX > absZ) {
315
+ indicatorClass = direction.x > 0 ? '.damage-indicator.right' : '.damage-indicator.left';
316
+ } else {
317
+ indicatorClass = direction.z > 0 ? '.damage-indicator.top' : '.damage-indicator.bottom';
318
+ }
319
+
320
+ // Show and animate the indicator
321
+ const indicator = document.querySelector(indicatorClass);
322
+ if (indicator) {
323
+ indicator.classList.add('active');
324
+ indicator.style.animation = 'none';
325
+ indicator.offsetHeight; // Force reflow
326
+ indicator.style.animation = 'fadeOut 1s forwards';
327
+ }
328
+ }
329
+
330
+ if (type === 'show-damage') {
331
+ const { damage } = data;
332
+
333
+ // Create a new damage number element
334
+ const damageNumber = document.createElement('div');
335
+ damageNumber.className = 'hit-damage-number';
336
+ damageNumber.textContent = damage;
337
+
338
+ // Add some randomness to position
339
+ const randomX = Math.random() * 60 - 30; // -30 to 30px
340
+ const randomY = Math.random() * 40 - 20; // -20 to 20px
341
+
342
+ damageNumber.style.transform = `translate(${randomX}px, ${randomY}px)`;
343
+
344
+ // Add to container
345
+ const container = document.querySelector('.hit-damage-container');
346
+ container.appendChild(damageNumber);
347
+
348
+ // Remove after animation completes
349
+ setTimeout(() => {
350
+ damageNumber.remove();
351
+ }, 500);
352
+ }
353
+
354
+ if (type === 'health') {
355
+ const { health, maxHealth } = data;
356
+ const healthText = document.querySelector('.health-text');
357
+ const healthBarFill = document.querySelector('.health-bar-fill');
358
+
359
+ healthText.textContent = health;
360
+ healthBarFill.style.width = `${(health / maxHealth) * 100}%`;
361
+ }
362
+
363
+ if (type === 'inventory') {
364
+ const { inventory } = data;
365
+
366
+ inventory.forEach((item, i) => {
367
+ const slot = document.querySelector(`.inventory-slot[data-slot="${i}"]`);
368
+ const icon = slot.querySelector('.slot-icon');
369
+ const name = slot.querySelector('.slot-name');
370
+ const quantity = slot.querySelector('.slot-quantity');
371
+
372
+ icon.style.display = item ? 'block' : 'none';
373
+ name.style.display = item ? 'block' : 'none';
374
+ quantity.style.display = item ? 'block' : 'none';
375
+
376
+ if (!item) {
377
+ return;
378
+ }
379
+
380
+ icon.src = `${CDN_ASSETS_URL}/${item.iconImageUri}`;
381
+ name.textContent = item.name;
382
+ quantity.textContent = item.quantity !== -1 ? item.quantity : '∞';
383
+ });
384
+ }
385
+
386
+ if (type === 'inventory-active-slot') {
387
+ const { index } = data;
388
+
389
+ // Remove active slot class from all slots
390
+ document.querySelectorAll('.inventory-slot').forEach(slot => {
391
+ slot.classList.remove('inventory-active-slot');
392
+ });
393
+
394
+ // Add active slot class to selected slot
395
+ const activeSlot = document.querySelector(`.inventory-slot[data-slot="${index}"]`);
396
+ if (activeSlot) {
397
+ activeSlot.classList.add('inventory-active-slot');
398
+ }
399
+ }
400
+
401
+ if (type === 'inventory-quantity-update') {
402
+ const { index, quantity } = data;
403
+
404
+ const slot = document.querySelector(`.inventory-slot[data-slot="${index}"]`);
405
+ const quantityElement = slot.querySelector('.slot-quantity');
406
+
407
+ quantityElement.textContent = quantity;
408
+ }
409
+
410
+ if (type === 'leaderboard-sync') {
411
+ const { killCounts } = data;
412
+ leaderboardKillCounts = killCounts;
413
+ updateLeaderboard();
414
+ }
415
+
416
+ if (type === 'leaderboard-update') {
417
+ const { username, killCount } = data;
418
+ leaderboardKillCounts[username] = killCount;
419
+ updateLeaderboard();
420
+ }
421
+
422
+ if (type === 'players-count') {
423
+ const { count } = data;
424
+ const playersCount = document.querySelector('.leaderboard-players-count');
425
+ playersCount.textContent = `Players: ${count}`;
426
+ }
427
+
428
+ if (type === 'materials') {
429
+ const { materials } = data;
430
+ const materialsAmount = document.querySelector('.materials-amount');
431
+ const currentMaterials = parseInt(materialsAmount.textContent);
432
+ const difference = materials - currentMaterials;
433
+
434
+ if (difference !== 0) {
435
+ const materialsCounter = document.querySelector('.materials-counter');
436
+ const floatingNumber = document.createElement('div');
437
+ floatingNumber.className = 'floating-number';
438
+ floatingNumber.textContent = difference > 0 ? `+${difference}` : difference;
439
+ materialsCounter.appendChild(floatingNumber);
440
+
441
+ // Remove the element after animation completes
442
+ setTimeout(() => {
443
+ floatingNumber.remove();
444
+ }, 1000);
445
+ }
446
+
447
+ materialsAmount.textContent = materials;
448
+ }
449
+
450
+ if (type === 'shield') {
451
+ const { shield, maxShield } = data;
452
+ const shieldText = document.querySelector('.shield-text');
453
+ const shieldBarFill = document.querySelector('.shield-bar-fill');
454
+
455
+ shieldText.textContent = shield;
456
+ shieldBarFill.style.width = `${(shield / maxShield) * 100}%`;
457
+ }
458
+
459
+ if (type === 'scope-zoom') {
460
+ const { zoom } = data;
461
+ const scopeOverlay = document.querySelector('.scope-overlay');
462
+
463
+ if (zoom === 1) {
464
+ scopeOverlay.classList.remove('active');
465
+ } else {
466
+ scopeOverlay.classList.add('active');
467
+ }
468
+ }
469
+
470
+ if (type === 'timer-sync') {
471
+ const { startedAt, endsAt } = data;
472
+
473
+ // Clear any existing timer interval
474
+ if (timerInterval) {
475
+ clearInterval(timerInterval);
476
+ }
477
+
478
+ // Set the end time
479
+ gameEndTime = endsAt;
480
+
481
+ // Update timer immediately
482
+ updateTimer();
483
+
484
+ // Set up interval to update timer every second
485
+ timerInterval = setInterval(updateTimer, 1000);
486
+ }
487
+ });
488
+ </script>
489
+
490
+ <!-- UI Styles -->
491
+ <style>
492
+ .crosshair {
493
+ position: fixed;
494
+ top: 50%;
495
+ left: 50%;
496
+ transform: translate(-50%, -50%);
497
+ width: 20px;
498
+ height: 20px;
499
+ pointer-events: none;
500
+ opacity: 0.7;
501
+ }
502
+
503
+ .crosshair::before,
504
+ .crosshair::after {
505
+ content: '';
506
+ position: absolute;
507
+ background-color: rgba(255, 255, 255, 0.8);
508
+ }
509
+
510
+ .crosshair::before {
511
+ width: 2px;
512
+ height: 100%;
513
+ left: 50%;
514
+ transform: translateX(-50%);
515
+ }
516
+
517
+ .crosshair::after {
518
+ width: 100%;
519
+ height: 2px;
520
+ top: 50%;
521
+ transform: translateY(-50%);
522
+ }
523
+
524
+ .game-start-announcement {
525
+ position: fixed;
526
+ top: 50%;
527
+ left: 50%;
528
+ transform: translate(-50%, -50%);
529
+ font-family: 'Inter', sans-serif;
530
+ font-size: 60px;
531
+ font-weight: bold;
532
+ color: #ff3333;
533
+ text-shadow: 0 0 10px rgba(0, 0, 0, 0.7), 0 0 20px rgba(0, 0, 0, 0.5);
534
+ text-transform: uppercase;
535
+ letter-spacing: 2px;
536
+ opacity: 0;
537
+ z-index: 1000;
538
+ pointer-events: none;
539
+ text-align: center;
540
+ }
541
+
542
+ .game-start-announcement.active {
543
+ animation: announcementFade 3s ease-in-out forwards;
544
+ }
545
+
546
+ @keyframes announcementFade {
547
+ 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
548
+ 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
549
+ 80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
550
+ 100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
551
+ }
552
+
553
+ .hit-damage-container {
554
+ position: fixed;
555
+ top: 50%;
556
+ left: 50%;
557
+ transform: translate(-50%, -50%);
558
+ pointer-events: none;
559
+ z-index: 100;
560
+ width: 100px;
561
+ height: 100px;
562
+ display: flex;
563
+ justify-content: center;
564
+ align-items: center;
565
+ }
566
+
567
+ .hit-damage-number {
568
+ color: white;
569
+ font-family: 'Inter', sans-serif;
570
+ font-size: 18px;
571
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
572
+ animation: damagePopup 0.5s ease-out forwards;
573
+ position: absolute;
574
+ }
575
+
576
+ @keyframes damagePopup {
577
+ 0% {
578
+ opacity: 0;
579
+ transform: scale(0.5) translateY(0);
580
+ }
581
+ 20% {
582
+ opacity: 1;
583
+ transform: scale(1.2) translateY(0);
584
+ }
585
+ 100% {
586
+ opacity: 0;
587
+ transform: scale(1) translateY(-50px);
588
+ }
589
+ }
590
+
591
+ .materials-counter {
592
+ position: fixed;
593
+ bottom: 120px;
594
+ right: 20px;
595
+ background: rgba(0, 0, 0, 0.5);
596
+ padding: 8px 15px;
597
+ border-radius: 5px;
598
+ display: flex;
599
+ align-items: center;
600
+ gap: 10px;
601
+ font-family: 'Inter', sans-serif;
602
+ color: #ffffff;
603
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
604
+ border: 2px solid rgba(255, 255, 255, 0.3);
605
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
606
+ }
607
+
608
+ .floating-number {
609
+ position: absolute;
610
+ color: #ffffff;
611
+ font-weight: bold;
612
+ font-size: 16px;
613
+ pointer-events: none;
614
+ animation: floatUp 1s ease-out forwards;
615
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
616
+ }
617
+
618
+ @keyframes floatUp {
619
+ 0% {
620
+ opacity: 1;
621
+ transform: translateY(0);
622
+ }
623
+ 100% {
624
+ opacity: 0;
625
+ transform: translateY(-50px);
626
+ }
627
+ }
628
+
629
+ .materials-icon {
630
+ width: 20px;
631
+ height: 20px;
632
+ object-fit: contain;
633
+ }
634
+
635
+ .materials-amount {
636
+ font-size: 16px;
637
+ font-weight: bold;
638
+ }
639
+
640
+ .hud {
641
+ position: fixed;
642
+ bottom: 20px;
643
+ left: 50%;
644
+ transform: translateX(-50%);
645
+ display: flex;
646
+ flex-direction: column;
647
+ z-index: 99;
648
+ gap: 10px;
649
+ font-family: 'Inter', sans-serif;
650
+ text-transform: uppercase;
651
+ color: #ffffff;
652
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
653
+ }
654
+
655
+ .ammo-indicator {
656
+ text-align: center;
657
+ font-size: 18px;
658
+ font-weight: bold;
659
+ margin-bottom: 5px;
660
+ background: rgba(0, 0, 0, 0.5);
661
+ padding: 5px 15px;
662
+ border-radius: 3px;
663
+ box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
664
+ display: flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ user-select: none;
668
+ gap: 10px;
669
+ }
670
+
671
+ .ammo-icon {
672
+ width: 18px;
673
+ height: 18px;
674
+ object-fit: contain;
675
+ position: relative;
676
+ top: 2px;
677
+ }
678
+
679
+ .ammo-divider {
680
+ margin: 0 5px;
681
+ opacity: 0.7;
682
+ }
683
+
684
+ .inventory-hud {
685
+ position: fixed;
686
+ bottom: 20px;
687
+ right: 20px;
688
+ display: flex;
689
+ gap: 10px;
690
+ z-index: 99;
691
+ }
692
+
693
+ .inventory-slot {
694
+ width: 60px;
695
+ height: 60px;
696
+ background: rgba(0, 0, 0, 0.5);
697
+ border-radius: 5px;
698
+ border: 2px solid rgba(255, 255, 255, 0.3);
699
+ display: flex;
700
+ align-items: center;
701
+ justify-content: center;
702
+ position: relative;
703
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
704
+ }
705
+
706
+ .inventory-active-slot {
707
+ border-color: rgba(255, 255, 255, 0.6);
708
+ }
709
+
710
+ .slot-number {
711
+ position: absolute;
712
+ top: -20px;
713
+ left: 50%;
714
+ transform: translateX(-50%);
715
+ font-family: 'Inter', sans-serif;
716
+ font-size: 14px;
717
+ font-weight: bold;
718
+ color: rgba(255, 255, 255, 0.8);
719
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
720
+ }
721
+
722
+ .slot-icon {
723
+ max-width: 70%;
724
+ max-height: 70%;
725
+ object-fit: contain;
726
+ }
727
+
728
+ .slot-quantity {
729
+ position: absolute;
730
+ bottom: 2px;
731
+ left: 2px;
732
+ font-family: 'Inter', sans-serif;
733
+ font-size: 12px;
734
+ font-weight: bold;
735
+ color: rgba(255, 255, 255, 0.9);
736
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
737
+ background: rgba(0, 0, 0, 0.5);
738
+ padding: 1px 4px;
739
+ border-radius: 3px;
740
+ }
741
+
742
+ .slot-name {
743
+ position: absolute;
744
+ bottom: -18px;
745
+ left: 50%;
746
+ transform: translateX(-50%);
747
+ font-family: 'Inter', sans-serif;
748
+ font-size: 10px;
749
+ font-weight: bold;
750
+ color: rgba(255, 255, 255, 0.8);
751
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
752
+ white-space: nowrap;
753
+ }
754
+
755
+ .info-container {
756
+ display: flex;
757
+ align-items: center;
758
+ gap: 10px;
759
+ flex-direction: column;
760
+ }
761
+
762
+ .shield-bar {
763
+ width: 250px;
764
+ height: 20px;
765
+ background: rgba(0, 0, 0, 0.5);
766
+ border-radius: 3px;
767
+ overflow: hidden;
768
+ box-shadow: 0 0 10px rgba(0, 0, 255, 0.3);
769
+ position: relative;
770
+ }
771
+
772
+ .shield-bar-fill {
773
+ width: 0%;
774
+ height: 100%;
775
+ background: linear-gradient(to right, #0000ff, #3333ff);
776
+ transition: width 0.3s ease;
777
+ }
778
+
779
+ .shield-icon {
780
+ position: absolute;
781
+ left: 5px;
782
+ top: 50%;
783
+ transform: translateY(-50%);
784
+ height: 12px;
785
+ width: 12px;
786
+ z-index: 1;
787
+ }
788
+
789
+ .shield-text {
790
+ position: absolute;
791
+ left: 50%;
792
+ top: 50%;
793
+ transform: translate(-50%, -50%);
794
+ font-size: 0.8em;
795
+ font-weight: bold;
796
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
797
+ z-index: 1;
798
+ }
799
+
800
+ .health-bar {
801
+ width: 250px;
802
+ height: 20px;
803
+ background: rgba(0, 0, 0, 0.5);
804
+ border-radius: 3px;
805
+ overflow: hidden;
806
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.3);
807
+ position: relative;
808
+ }
809
+
810
+ .health-bar-fill {
811
+ width: 100%;
812
+ height: 100%;
813
+ background: linear-gradient(to right, #ff0000, #ff3333);
814
+ transition: width 0.3s ease;
815
+ }
816
+
817
+ .health-icon {
818
+ position: absolute;
819
+ left: 5px;
820
+ top: 50%;
821
+ transform: translateY(-50%);
822
+ height: 12px;
823
+ width: 12px;
824
+ z-index: 1;
825
+ }
826
+
827
+ .health-text {
828
+ position: absolute;
829
+ left: 50%;
830
+ top: 50%;
831
+ transform: translate(-50%, -50%);
832
+ font-size: 0.8em;
833
+ font-weight: bold;
834
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
835
+ z-index: 1;
836
+ }
837
+
838
+ .item-label {
839
+ background-color: rgba(0, 0, 0, 0.6);
840
+ padding: 12px 20px;
841
+ border-radius: 4px;
842
+ text-align: center;
843
+ color: #ffffff;
844
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
845
+ font-family: 'Inter', sans-serif;
846
+ text-transform: uppercase;
847
+ position: relative;
848
+ max-width: 220px;
849
+ margin: 0 auto;
850
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
851
+ }
852
+
853
+ .label-name {
854
+ font-size: 20px;
855
+ margin-bottom: 6px;
856
+ font-weight: bold;
857
+ }
858
+
859
+ .label-quantity {
860
+ position: absolute;
861
+ top: -12px;
862
+ right: -10px;
863
+ background: #ffb700;
864
+ color: black;
865
+ font-weight: bold;
866
+ padding: 2px 4px;
867
+ font-size: 14px;
868
+ text-transform: none;
869
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
870
+ text-shadow: none;
871
+ border: 1px solid #ffffff;
872
+ }
873
+
874
+ .label-prompt {
875
+ font-size: 14px;
876
+ opacity: 0.8;
877
+ }
878
+
879
+ .label-caret {
880
+ position: absolute;
881
+ bottom: -8px;
882
+ left: 50%;
883
+ transform: translateX(-50%);
884
+ width: 0;
885
+ height: 0;
886
+ border-left: 8px solid transparent;
887
+ border-right: 8px solid transparent;
888
+ border-top: 8px solid rgba(0, 0, 0, 0.6);
889
+ }
890
+
891
+ .scope-overlay {
892
+ position: fixed;
893
+ top: 0;
894
+ left: 0;
895
+ pointer-events: none;
896
+ width: 100%;
897
+ height: 100%;
898
+ z-index: 99;
899
+ object-fit: cover;
900
+ opacity: 0;
901
+ transition: opacity 0.3s ease;
902
+ }
903
+
904
+ .scope-overlay.active {
905
+ opacity: 1;
906
+ }
907
+
908
+ #chat-window {
909
+ width: 25% !important;
910
+ }
911
+
912
+ .damage-indicator-container {
913
+ position: fixed;
914
+ top: 0;
915
+ left: 0;
916
+ width: 100%;
917
+ height: 100%;
918
+ pointer-events: none;
919
+ z-index: 5;
920
+ }
921
+
922
+ .damage-indicator {
923
+ position: absolute;
924
+ opacity: 0;
925
+ background: linear-gradient(to center, rgba(255,0,0,0.7), rgba(255,0,0,0));
926
+ }
927
+
928
+ .damage-indicator.active {
929
+ opacity: 1;
930
+ }
931
+
932
+ /* Top damage indicator */
933
+ .damage-indicator.top {
934
+ top: 0;
935
+ left: 0;
936
+ width: 100%;
937
+ height: 150px;
938
+ background: linear-gradient(to bottom, rgba(255,0,0,0.7), rgba(255,0,0,0));
939
+ }
940
+
941
+ /* Right damage indicator */
942
+ .damage-indicator.right {
943
+ top: 0;
944
+ right: 0;
945
+ width: 150px;
946
+ height: 100%;
947
+ background: linear-gradient(to left, rgba(255,0,0,0.7), rgba(255,0,0,0));
948
+ }
949
+
950
+ /* Bottom damage indicator */
951
+ .damage-indicator.bottom {
952
+ bottom: 0;
953
+ left: 0;
954
+ width: 100%;
955
+ height: 150px;
956
+ background: linear-gradient(to top, rgba(255,0,0,0.7), rgba(255,0,0,0));
957
+ }
958
+
959
+ /* Left damage indicator */
960
+ .damage-indicator.left {
961
+ top: 0;
962
+ left: 0;
963
+ width: 150px;
964
+ height: 100%;
965
+ background: linear-gradient(to right, rgba(255,0,0,0.7), rgba(255,0,0,0));
966
+ }
967
+
968
+ @keyframes fadeOut {
969
+ 0% { opacity: 1; }
970
+ 100% { opacity: 0; }
971
+ }
972
+
973
+ /* Leaderboard Styles */
974
+ .leaderboard {
975
+ position: fixed;
976
+ top: 20px;
977
+ right: 20px;
978
+ background: rgba(0, 0, 0, 0.7);
979
+ border-radius: 5px;
980
+ padding: 10px;
981
+ width: 200px;
982
+ font-family: 'Arial', sans-serif;
983
+ color: white;
984
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
985
+ border: 2px solid rgba(255, 255, 255, 0.2);
986
+ z-index: 100;
987
+ }
988
+
989
+ .leaderboard-title {
990
+ text-align: center;
991
+ font-size: 18px;
992
+ font-weight: bold;
993
+ padding-bottom: 5px;
994
+ margin-bottom: 5px;
995
+ border-bottom: 1px solid rgba(255, 255, 255, 0.3);
996
+ text-transform: uppercase;
997
+ letter-spacing: 1px;
998
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
999
+ }
1000
+
1001
+ .leaderboard-timer {
1002
+ text-align: center;
1003
+ font-size: 16px;
1004
+ font-weight: bold;
1005
+ margin-bottom: 10px;
1006
+ padding: 3px;
1007
+ background: rgba(255, 255, 255, 0.1);
1008
+ border-radius: 3px;
1009
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
1010
+ }
1011
+
1012
+ .leaderboard-header {
1013
+ display: flex;
1014
+ justify-content: space-between;
1015
+ padding: 3px 8px;
1016
+ font-size: 12px;
1017
+ font-weight: bold;
1018
+ text-transform: uppercase;
1019
+ color: rgba(255, 255, 255, 0.7);
1020
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
1021
+ margin-bottom: 5px;
1022
+ }
1023
+
1024
+ .leaderboard-players {
1025
+ display: flex;
1026
+ flex-direction: column;
1027
+ gap: 5px;
1028
+ }
1029
+
1030
+ .leaderboard-player {
1031
+ display: flex;
1032
+ justify-content: space-between;
1033
+ padding: 5px 8px;
1034
+ background: rgba(255, 255, 255, 0.1);
1035
+ border-radius: 3px;
1036
+ transition: background 0.2s ease;
1037
+ }
1038
+
1039
+ .leaderboard-player:hover {
1040
+ background: rgba(255, 255, 255, 0.2);
1041
+ }
1042
+
1043
+ .leaderboard-players-count {
1044
+ font-size: 14px;
1045
+ font-weight: bold;
1046
+ color: rgba(255, 255, 255, 0.7);
1047
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
1048
+ text-transform: uppercase;
1049
+ text-align: center;
1050
+ margin-bottom: 10px;
1051
+ padding: 3px;
1052
+ background: rgba(255, 255, 255, 0.1);
1053
+ border-radius: 3px;
1054
+ }
1055
+
1056
+ .rank-icon {
1057
+ width: 16px;
1058
+ height: 16px;
1059
+ margin-right: 5px;
1060
+ position: relative;
1061
+ top: 1px;
1062
+ }
1063
+
1064
+ .player-name {
1065
+ font-weight: bold;
1066
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
1067
+ white-space: nowrap;
1068
+ overflow: hidden;
1069
+ max-width: 160px;
1070
+ text-overflow: ellipsis;
1071
+ }
1072
+ </style>