hytopia 0.3.6 → 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 (117) 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/explosion/explosion-named-nodes.glb +0 -0
  44. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb +0 -0
  45. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb.md5 +1 -0
  46. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2-named-nodes.glb +0 -0
  47. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb +0 -0
  48. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb.md5 +1 -0
  49. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3-named-nodes.glb +0 -0
  50. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb +0 -0
  51. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb.md5 +1 -0
  52. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter-named-nodes.glb +0 -0
  53. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb +0 -0
  54. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb.md5 +1 -0
  55. package/examples/hygrounds/assets/models/environment/chest.gltf +1 -0
  56. package/examples/hygrounds/assets/models/environment/explosion.glb +0 -0
  57. package/examples/hygrounds/assets/models/environment/muzzle-flash.gltf +1 -0
  58. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit-named-nodes.glb +0 -0
  59. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb +0 -0
  60. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb.md5 +1 -0
  61. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack-named-nodes.glb +0 -0
  62. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb +0 -0
  63. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb.md5 +1 -0
  64. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill-named-nodes.glb +0 -0
  65. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb +0 -0
  66. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb.md5 +1 -0
  67. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile-named-nodes.glb +0 -0
  68. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb +0 -0
  69. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb.md5 +1 -0
  70. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion-named-nodes.glb +0 -0
  71. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb +0 -0
  72. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb.md5 +1 -0
  73. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2-named-nodes.glb +0 -0
  74. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb +0 -0
  75. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb.md5 +1 -0
  76. package/examples/hygrounds/assets/models/items/ak-47.glb +0 -0
  77. package/examples/hygrounds/assets/models/items/auto-shotgun.glb +0 -0
  78. package/examples/hygrounds/assets/models/items/bolt-action-sniper.glb +0 -0
  79. package/examples/hygrounds/assets/models/items/light-machine-gun.glb +0 -0
  80. package/examples/hygrounds/assets/models/items/medpack.glb +0 -0
  81. package/examples/hygrounds/assets/models/items/mining-drill.glb +0 -0
  82. package/examples/hygrounds/assets/models/items/pickaxe.gltf +1 -0
  83. package/examples/hygrounds/assets/models/items/pistol.glb +0 -0
  84. package/examples/hygrounds/assets/models/items/rocket-launcher.glb +0 -0
  85. package/examples/hygrounds/assets/models/items/rocket-missile.glb +0 -0
  86. package/examples/hygrounds/assets/models/items/shield-potion.glb +0 -0
  87. package/examples/hygrounds/assets/models/items/shotgun.glb +0 -0
  88. package/examples/hygrounds/assets/models/players/soldier-player.gltf +1 -0
  89. package/examples/hygrounds/assets/ui/images/scope.png +0 -0
  90. package/examples/hygrounds/assets/ui/index.html +1072 -0
  91. package/examples/hygrounds/bun.lock +503 -0
  92. package/examples/hygrounds/classes/ChestEntity.ts +133 -0
  93. package/examples/hygrounds/classes/GameManager.ts +384 -0
  94. package/examples/hygrounds/classes/GamePlayerEntity.ts +564 -0
  95. package/examples/hygrounds/classes/GunEntity.ts +263 -0
  96. package/examples/hygrounds/classes/ItemEntity.ts +225 -0
  97. package/examples/hygrounds/classes/ItemFactory.ts +49 -0
  98. package/examples/hygrounds/classes/MeleeWeaponEntity.ts +138 -0
  99. package/examples/hygrounds/classes/TerrainDamageManager.ts +56 -0
  100. package/examples/hygrounds/classes/items/MedPackEntity.ts +43 -0
  101. package/examples/hygrounds/classes/items/ShieldPotionEntity.ts +43 -0
  102. package/examples/hygrounds/classes/weapons/AK47Entity.ts +43 -0
  103. package/examples/hygrounds/classes/weapons/AutoShotgunEntity.ts +80 -0
  104. package/examples/hygrounds/classes/weapons/BoltActionSniperEntity.ts +46 -0
  105. package/examples/hygrounds/classes/weapons/LightMachineGunEntity.ts +43 -0
  106. package/examples/hygrounds/classes/weapons/MiningDrillEntity.ts +38 -0
  107. package/examples/hygrounds/classes/weapons/PickaxeEntity.ts +38 -0
  108. package/examples/hygrounds/classes/weapons/PistolEntity.ts +46 -0
  109. package/examples/hygrounds/classes/weapons/RocketLauncherEntity.ts +186 -0
  110. package/examples/hygrounds/classes/weapons/ShotgunEntity.ts +84 -0
  111. package/examples/hygrounds/gameConfig.ts +398 -0
  112. package/examples/hygrounds/index.ts +40 -0
  113. package/examples/hygrounds/package.json +16 -0
  114. package/package.json +1 -1
  115. package/server.api.json +21 -0
  116. package/server.d.ts +2 -1
  117. package/server.js +113 -113
@@ -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>