hytopia 0.1.98 → 0.2.0

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 (68) hide show
  1. package/docs/server.entity.height.md +13 -0
  2. package/docs/server.entity.md +22 -1
  3. package/docs/server.entity.opacity.md +1 -1
  4. package/docs/server.modelregistry.getheight.md +55 -0
  5. package/docs/server.modelregistry.md +14 -0
  6. package/docs/server.player.md +21 -0
  7. package/docs/server.player.profilepictureurl.md +13 -0
  8. package/examples/pathfinding/index.ts +4 -4
  9. package/examples/payload-game/index.ts +12 -10
  10. package/examples/zombies-fps/README.md +5 -0
  11. package/examples/zombies-fps/assets/audio/music/bg.mp3 +0 -0
  12. package/examples/zombies-fps/assets/audio/sfx/pistol-reload.mp3 +0 -0
  13. package/examples/zombies-fps/assets/audio/sfx/pistol-shoot.mp3 +0 -0
  14. package/examples/zombies-fps/assets/audio/sfx/player-hurt.mp3 +0 -0
  15. package/examples/zombies-fps/assets/audio/sfx/purchase.mp3 +0 -0
  16. package/examples/zombies-fps/assets/audio/sfx/rifle-reload.mp3 +0 -0
  17. package/examples/zombies-fps/assets/audio/sfx/rifle-shoot.mp3 +0 -0
  18. package/examples/zombies-fps/assets/audio/sfx/ripper-idle.mp3 +0 -0
  19. package/examples/zombies-fps/assets/audio/sfx/roulette.mp3 +0 -0
  20. package/examples/zombies-fps/assets/audio/sfx/shotgun-reload.mp3 +0 -0
  21. package/examples/zombies-fps/assets/audio/sfx/shotgun-shoot.mp3 +0 -0
  22. package/examples/zombies-fps/assets/audio/sfx/wave-start.mp3 +0 -0
  23. package/examples/zombies-fps/assets/audio/sfx/zombie-idle.mp3 +0 -0
  24. package/examples/zombies-fps/assets/icons/ak-47.png +0 -0
  25. package/examples/zombies-fps/assets/icons/ar-15.png +0 -0
  26. package/examples/zombies-fps/assets/icons/auto-pistol.png +0 -0
  27. package/examples/zombies-fps/assets/icons/auto-shotgun.png +0 -0
  28. package/examples/zombies-fps/assets/icons/heart.png +0 -0
  29. package/examples/zombies-fps/assets/icons/pistol.png +0 -0
  30. package/examples/zombies-fps/assets/icons/shotgun.png +0 -0
  31. package/examples/zombies-fps/assets/models/environment/bombbox.gltf +1 -0
  32. package/examples/zombies-fps/assets/models/environment/bullet-hole.gltf +1 -0
  33. package/examples/zombies-fps/assets/models/environment/healthkit.gltf +1 -0
  34. package/examples/zombies-fps/assets/models/environment/muzzle-flash.gltf +1 -0
  35. package/examples/zombies-fps/assets/models/items/ak-47.glb +0 -0
  36. package/examples/zombies-fps/assets/models/items/ar-15.glb +0 -0
  37. package/examples/zombies-fps/assets/models/items/auto-pistol.glb +0 -0
  38. package/examples/zombies-fps/assets/models/items/auto-shotgun.glb +0 -0
  39. package/examples/zombies-fps/assets/models/items/shotgun.glb +0 -0
  40. package/examples/zombies-fps/assets/models/npcs/ripper-boss.gltf +1 -0
  41. package/examples/zombies-fps/assets/models/players/soldier-player.gltf +1 -1
  42. package/examples/zombies-fps/assets/models/projectiles/bullet-trace.gltf +1 -0
  43. package/examples/zombies-fps/assets/ui/index.html +620 -27
  44. package/examples/zombies-fps/classes/EnemyEntity.ts +183 -4
  45. package/examples/zombies-fps/classes/GameManager.ts +165 -0
  46. package/examples/zombies-fps/classes/GamePlayerEntity.ts +263 -14
  47. package/examples/zombies-fps/classes/GunEntity.ts +225 -13
  48. package/examples/zombies-fps/classes/InteractableEntity.ts +9 -0
  49. package/examples/zombies-fps/classes/PurchaseBarrierEntity.ts +70 -17
  50. package/examples/zombies-fps/classes/WeaponCrateEntity.ts +173 -0
  51. package/examples/zombies-fps/classes/enemies/RipperEntity.ts +67 -0
  52. package/examples/zombies-fps/classes/enemies/ZombieEntity.ts +30 -0
  53. package/examples/zombies-fps/classes/guns/AK47Entity.ts +43 -0
  54. package/examples/zombies-fps/classes/guns/AR15Entity.ts +32 -0
  55. package/examples/zombies-fps/classes/guns/AutoPistolEntity.ts +36 -0
  56. package/examples/zombies-fps/classes/guns/AutoShotgunEntity.ts +37 -0
  57. package/examples/zombies-fps/classes/guns/PistolEntity.ts +23 -15
  58. package/examples/zombies-fps/classes/guns/ShotgunEntity.ts +92 -0
  59. package/examples/zombies-fps/gameConfig.ts +125 -21
  60. package/examples/zombies-fps/index.ts +14 -31
  61. package/package.json +1 -1
  62. package/server.api.json +126 -18
  63. package/server.d.ts +17 -5
  64. package/server.js +98 -90
  65. package/tsdoc-metadata.json +1 -1
  66. package/examples/zombies-fps/assets/audio/sfx/pistol-shoot-1.mp3 +0 -0
  67. package/examples/zombies-fps/assets/audio/sfx/pistol-shoot-2.mp3 +0 -0
  68. package/examples/zombies-fps/classes/guns/BulletEntity.ts +0 -0
@@ -0,0 +1 @@
1
+ {"asset":{"version":"2.0","generator":"Blockbench 4.12.2 glTF exporter"},"scenes":[{"nodes":[1],"name":"blockbench_export"}],"scene":0,"nodes":[{"translation":[0,0,-1.75],"name":"cube","mesh":0},{"children":[0]}],"bufferViews":[{"buffer":0,"byteOffset":0,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":288,"byteLength":288,"target":34962,"byteStride":12},{"buffer":0,"byteOffset":576,"byteLength":192,"target":34962,"byteStride":8},{"buffer":0,"byteOffset":768,"byteLength":72,"target":34963}],"buffers":[{"byteLength":840,"uri":"data:application/octet-stream;base64,AAAAAAAAgD0AAGRAAAAAAAAAgD0AAIC9AAAAAAAAAAAAAGRAAAAAAAAAAAAAAIC9AACAvQAAgD0AAIC9AACAvQAAgD0AAGRAAACAvQAAAAAAAIC9AACAvQAAAAAAAGRAAACAvQAAgD0AAIC9AAAAAAAAgD0AAIC9AACAvQAAgD0AAGRAAAAAAAAAgD0AAGRAAACAvQAAAAAAAGRAAAAAAAAAAAAAAGRAAACAvQAAAAAAAIC9AAAAAAAAAAAAAIC9AACAvQAAgD0AAGRAAAAAAAAAgD0AAGRAAACAvQAAAAAAAGRAAAAAAAAAAAAAAGRAAAAAAAAAgD0AAIC9AACAvQAAgD0AAIC9AAAAAAAAAAAAAIC9AACAvQAAAAAAAIC9AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAGg/AAAAAAAAAAAAAIA8AABoPwAAgDwAAAAAAACAPAAAaD8AAIA8AAAAAAAAAD0AAGg/AAAAPQAAgDwAAHA/AAAAAAAAcD8AAIA8AAAAPQAAAAAAAAA9AAAAPQAAAD0AAIA8AAAAPQAAAD0AAHA/AACAPAAAcD8AAAA9AABAPQAAQD0AAEA9AAAAPQAAgD0AAEA9AACAPQAAAD0AAAA9AABAPQAAAD0AAAA9AABAPQAAQD0AAEA9AAACAAEAAgADAAEABAAGAAUABgAHAAUACAAKAAkACgALAAkADAAOAA0ADgAPAA0AEAASABEAEgATABEAFAAWABUAFgAXABUA"}],"accessors":[{"bufferView":0,"componentType":5126,"count":24,"max":[0,0.0625,3.5625],"min":[-0.0625,0,-0.0625],"type":"VEC3"},{"bufferView":1,"componentType":5126,"count":24,"max":[1,1,1],"min":[-1,-1,-1],"type":"VEC3"},{"bufferView":2,"componentType":5126,"count":24,"max":[0.90625,0.9375],"min":[0,0],"type":"VEC2"},{"bufferView":3,"componentType":5123,"count":36,"max":[23],"min":[0],"type":"SCALAR"}],"materials":[{"pbrMetallicRoughness":{"metallicFactor":0,"roughnessFactor":1,"baseColorTexture":{"index":0}},"alphaMode":"MASK","alphaCutoff":0.05,"doubleSided":true}],"textures":[{"sampler":0,"source":0,"name":"bullet_texture.png"}],"samplers":[{"magFilter":9728,"minFilter":9728,"wrapS":33071,"wrapT":33071}],"images":[{"mimeType":"image/png","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAMlJREFUeF7t2YENwjAQQ1FuHIZhWobpOK1QC0qlhAHOLxvEutj+l9q31/4IOPV81+yaRYBhAlYqdR6Q2wREC5B4+c9k/yaAAAuX7Pz+TYAnwAOYoBT4VmExKAbntKQHNFeACTLBax8gBaSAFJguDZuHABYQg2JQDJ4/Q3qAHqAH6AHdS8/f32EmyASZIBNkgoEKoEE0iAbRIBy2D7AQsRGyEoPDcBgOw+FAGPQ5CofhMByGw3AYDsPhYBxObH/jnSP7PwEGBeIn4AAkA/BBqMCpNQAAAABJRU5ErkJggg=="}],"meshes":[{"primitives":[{"mode":4,"attributes":{"POSITION":0,"NORMAL":1,"TEXCOORD_0":2},"indices":3,"material":0}]}]}
@@ -1,35 +1,628 @@
1
- <style>
2
- .crosshair {
3
- position: fixed;
4
- top: 50%;
5
- left: 50%;
6
- transform: translate(-50%, -50%);
7
- width: 20px;
8
- height: 20px;
9
- pointer-events: none;
10
- opacity: 0.7;
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=Creepster&display=swap" rel="stylesheet">
5
+
6
+ <!-- UI-->
7
+ <div class="boss-health-container">
8
+ <div class="boss-name"></div>
9
+ <div class="boss-health-bar">
10
+ <div class="boss-health-fill"></div>
11
+ </div>
12
+ </div>
13
+
14
+ <div class="crosshair"></div>
15
+ <div class="vignette"></div>
16
+ <div class="wave-announcement">Wave <span class="wave-number"></span></div>
17
+
18
+ <div class="game-info">
19
+ <div class="timer">Time: 00:00</div>
20
+ <div class="wave">Wave: 1</div>
21
+ </div>
22
+
23
+ <div class="hud">
24
+ <div class="weapon-info">
25
+ <div class="weapon-text">
26
+ <div class="weapon-name">Pistol</div>
27
+ <div class="weapon-ammo">Clip: 7/7</div>
28
+ </div>
29
+ <img src="" alt="Current Weapon" class="weapon-image">
30
+ </div>
31
+
32
+ <div class="info-container">
33
+ <div class="money-display">
34
+ <span class="money-symbol">$</span>
35
+ <span class="money-amount">0</span>
36
+ </div>
37
+ <div class="health-bar">
38
+ <div class="health-bar-fill"></div>
39
+ <img src="{{CDN_ASSETS_URL}}/icons/heart.png" alt="Health Icon" class="health-icon">
40
+ <div class="health-text">100/100</div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <template id="downed-player-template">
46
+ <div class="downed-player">
47
+ <div class="revive-text">Downed Player!</div>
48
+ <div class="revive-prompt">Hold "E" to revive, stay close!</div>
49
+ <div class="revive-progress">
50
+ <div class="revive-progress-fill"></div>
51
+ </div>
52
+ <div class="label-caret"></div>
53
+ </div>
54
+ </template>
55
+
56
+ <template id="purchase-label-template">
57
+ <div class="purchase-label">
58
+ <div class="label-name"></div>
59
+ <div class="label-cost"></div>
60
+ <div class="label-prompt">Press "E" to purchase</div>
61
+ <div class="label-caret"></div>
62
+ </div>
63
+ </template>
64
+
65
+ <template id="weapon-roulette-template">
66
+ <div class="weapon-roulette">
67
+ <img class="roulette-image" src="{{CDN_ASSETS_URL}}/icons/pistol.png" alt="Weapon">
68
+ <div class="roulette-name"></div>
69
+ <div class="roulette-prompt">Press "E" to equip</div>
70
+ </div>
71
+ </template>
72
+
73
+ <!-- UI Scripts-->
74
+ <script>
75
+ const CDN_ASSETS_URL = '{{CDN_ASSETS_URL}}';
76
+
77
+ hytopia.registerSceneUITemplate('downed-player', (id, onState) => {
78
+ const template = document.getElementById('downed-player-template');
79
+ const clone = template.content.cloneNode(true);
80
+ const progressFill = clone.querySelector('.revive-progress-fill');
81
+
82
+ onState(state => {
83
+ progressFill.style.width = `${state.progress}%`;
84
+ });
85
+
86
+ return clone;
87
+ });
88
+
89
+ // register purchase label template
90
+ hytopia.registerSceneUITemplate('purchase-label', (id, onState) => {
91
+ const template = document.getElementById('purchase-label-template');
92
+ const clone = template.content.cloneNode(true);
93
+ const labelName = clone.querySelector('.label-name');
94
+ const labelCost = clone.querySelector('.label-cost');
95
+
96
+ onState(state => {
97
+ labelName.textContent = state.name;
98
+ labelCost.textContent = `$${state.cost}`;
99
+ });
100
+
101
+ return clone;
102
+ });
103
+
104
+ // register weapon roulette template
105
+ hytopia.registerSceneUITemplate('weapon-roulette', (id, onState) => {
106
+ const template = document.getElementById('weapon-roulette-template');
107
+ const clone = template.content.cloneNode(true);
108
+ const rouletteImage = clone.querySelector('.roulette-image');
109
+ const rouletteName = clone.querySelector('.roulette-name');
110
+ const roulettePrompt = clone.querySelector('.roulette-prompt');
111
+ let rouletteInterval;
112
+
113
+ // Hide prompt initially
114
+ roulettePrompt.style.display = 'none';
115
+
116
+ onState(state => {
117
+ const { selectedWeaponId, possibleWeapons } = state;
118
+ let currentIndex = 0;
119
+
120
+ // Clear any existing interval
121
+ if (rouletteInterval) {
122
+ clearInterval(rouletteInterval);
123
+ }
124
+
125
+ // Start rapid weapon cycling
126
+ rouletteInterval = setInterval(() => {
127
+ const weapon = possibleWeapons[currentIndex];
128
+ rouletteImage.src = `${CDN_ASSETS_URL}/${weapon.iconUri}`;
129
+ console.log(rouletteImage.src);
130
+ rouletteName.textContent = weapon.name;
131
+ currentIndex = (currentIndex + 1) % possibleWeapons.length;
132
+ }, 100);
133
+
134
+ // After 2 seconds, stop on selected weapon and show prompt
135
+ setTimeout(() => {
136
+ clearInterval(rouletteInterval);
137
+ const selectedWeapon = possibleWeapons.find(w => w.id === selectedWeaponId);
138
+ if (selectedWeapon) {
139
+ rouletteImage.src = `${CDN_ASSETS_URL}/${selectedWeapon.iconUri}`;
140
+ rouletteName.textContent = selectedWeapon.name;
141
+ roulettePrompt.style.display = 'block';
142
+ }
143
+ }, 3000);
144
+ });
145
+
146
+ return clone;
147
+ });
148
+
149
+ // handle game data ui updates
150
+ hytopia.onData(data => {
151
+ const { type } = data;
152
+
153
+ if (!type) {
154
+ return console.warn('No type received for data', data);
11
155
  }
12
156
 
13
- .crosshair::before,
14
- .crosshair::after {
15
- content: '';
16
- position: absolute;
17
- background-color: rgba(255, 255, 255, 0.8);
157
+ if (type === 'start') {
158
+ document.querySelector('.game-info').style.display = 'block';
159
+ document.querySelector('.game-info .timer').textContent = 'Time: 00:00';
160
+ document.querySelector('.game-info .wave').textContent = 'Wave: 1';
161
+
162
+ // Start game timer
163
+ const startTime = Date.now();
164
+ setInterval(() => {
165
+ const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
166
+ const minutes = Math.floor(elapsedSeconds / 60).toString().padStart(2, '0');
167
+ const seconds = (elapsedSeconds % 60).toString().padStart(2, '0');
168
+ document.querySelector('.game-info .timer').textContent = `Time: ${minutes}:${seconds}`;
169
+ }, 1000);
18
170
  }
19
171
 
20
- .crosshair::before {
21
- width: 2px;
22
- height: 100%;
23
- left: 50%;
24
- transform: translateX(-50%);
172
+ if (type === 'wave') {
173
+ const { wave } = data;
174
+ document.querySelector('.game-info .wave').textContent = `Wave: ${wave}`;
175
+ showWaveAnnouncement(wave);
25
176
  }
26
177
 
27
- .crosshair::after {
28
- width: 100%;
29
- height: 2px;
30
- top: 50%;
31
- transform: translateY(-50%);
178
+ if (type === 'weapon') {
179
+ const { name, iconImageUri } = data;
180
+ document.querySelector('.weapon-name').textContent = name;
181
+ document.querySelector('.weapon-image').src = `${CDN_ASSETS_URL}/${iconImageUri}`;
32
182
  }
33
- </style>
34
183
 
35
- <div class="crosshair"></div>
184
+ if (type === 'ammo') {
185
+ const { ammo, maxAmmo } = data;
186
+ document.querySelector('.weapon-ammo').textContent = `CLIP: ${ammo}/${maxAmmo}`;
187
+ }
188
+
189
+ if (type === 'health') {
190
+ const { health, maxHealth } = data;
191
+ const percentage = (health / maxHealth) * 100;
192
+ document.querySelector('.health-bar-fill').style.width = `${percentage}%`;
193
+ document.querySelector('.health-text').textContent = `${health}/${maxHealth}`;
194
+
195
+ // Update vignette intensity based on health percentage
196
+ const vignette = document.querySelector('.vignette');
197
+ const vignetteOpacity = Math.max(0, (100 - percentage) / 100);
198
+ vignette.style.opacity = vignetteOpacity;
199
+ }
200
+
201
+ if (type === 'reload') {
202
+ document.querySelector('.weapon-ammo').textContent = 'RELOADING...';
203
+ }
204
+
205
+ if (type === 'money') {
206
+ const { money } = data;
207
+ const moneyAmount = Math.floor(money);
208
+ const moneyDisplay = document.querySelector('.money-display');
209
+ const moneyAmountEl = document.querySelector('.money-amount');
210
+
211
+ moneyAmountEl.textContent = moneyAmount;
212
+ // Adjust width based on number of digits (min 2 digits, max 8 digits)
213
+ const digits = Math.max(2, Math.min(8, moneyAmount.toString().length));
214
+ moneyDisplay.style.width = `${10 + (digits * 10)}px`;
215
+ }
216
+
217
+ if (type === 'boss') {
218
+ const { name, healthPercent, show } = data;
219
+ const bossContainer = document.querySelector('.boss-health-container');
220
+ const bossName = document.querySelector('.boss-name');
221
+ const bossHealthFill = document.querySelector('.boss-health-fill');
222
+ console.log(data);
223
+ if (show === true) {
224
+ bossContainer.style.display = 'block';
225
+ } else if (show === false) {
226
+ bossContainer.style.display = 'none';
227
+ bossHealthFill.style.width = '100%';
228
+ }
229
+
230
+ if (name) {
231
+ bossName.textContent = name;
232
+ }
233
+
234
+ if (healthPercent) {
235
+ bossHealthFill.style.width = `${healthPercent}%`;
236
+ }
237
+ }
238
+ });
239
+
240
+ function showWaveAnnouncement(waveNumber) {
241
+ const announcement = document.querySelector('.wave-announcement');
242
+ const numberSpan = announcement.querySelector('.wave-number');
243
+ numberSpan.textContent = waveNumber;
244
+ announcement.classList.add('show');
245
+ setTimeout(() => {
246
+ announcement.classList.remove('show');
247
+ }, 3000);
248
+ }
249
+ </script>
250
+
251
+ <!-- UI Styles -->
252
+ <style>
253
+ .boss-health-container {
254
+ display: none;
255
+ position: fixed;
256
+ top: 20px;
257
+ left: 50%;
258
+ transform: translateX(-50%);
259
+ width: 40%;
260
+ text-align: center;
261
+ }
262
+
263
+ .boss-name {
264
+ color: #ffffff;
265
+ font-family: 'Arial', sans-serif;
266
+ font-size: 24px;
267
+ font-weight: bold;
268
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
269
+ margin-bottom: 5px;
270
+ text-transform: uppercase;
271
+ }
272
+
273
+ .boss-health-bar {
274
+ width: 100%;
275
+ height: 10px;
276
+ background: rgba(0, 0, 0, 0.5);
277
+ border-radius: 3px;
278
+ overflow: hidden;
279
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.3);
280
+ }
281
+
282
+ .boss-health-fill {
283
+ width: 100%;
284
+ height: 100%;
285
+ background: linear-gradient(to right, #800000, #ff0000);
286
+ transition: width 0.3s ease;
287
+ }
288
+
289
+ .game-info {
290
+ display: none;
291
+ position: fixed;
292
+ top: 20px;
293
+ right: 20px;
294
+ }
295
+
296
+ .game-info div {
297
+ font-family: 'Arial', sans-serif;
298
+ font-size: 1em;
299
+ font-weight: bold;
300
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
301
+ color: #ffffff;
302
+ text-align: right;
303
+ margin-bottom: 5px;
304
+ }
305
+
306
+
307
+ .hud {
308
+ position: fixed;
309
+ bottom: 20px;
310
+ right: 20px;
311
+ display: flex;
312
+ flex-direction: column;
313
+ z-index: 99;
314
+ gap: 10px;
315
+ font-family: 'Arial', sans-serif;
316
+ text-transform: uppercase;
317
+ color: #ffffff;
318
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
319
+ }
320
+
321
+ .vignette {
322
+ position: fixed;
323
+ top: 0;
324
+ left: 0;
325
+ right: 0;
326
+ bottom: 0;
327
+ pointer-events: none;
328
+ background: radial-gradient(circle, transparent 30%, rgba(255, 0, 0, 0.6));
329
+ opacity: 0;
330
+ transition: opacity 0.3s ease;
331
+ z-index: 9;
332
+ }
333
+
334
+ .info-container {
335
+ display: flex;
336
+ align-items: center;
337
+ gap: 10px;
338
+ }
339
+
340
+ .money-display {
341
+ height: 20px;
342
+ background: rgba(0, 0, 0, 0.5);
343
+ border-radius: 3px;
344
+ padding: 0 10px;
345
+ display: flex;
346
+ align-items: center;
347
+ font-weight: bold;
348
+ font-size: 0.9em;
349
+ width: 30px; /* Initial width for 2 digits */
350
+ transition: width 0.3s ease;
351
+ white-space: nowrap;
352
+ }
353
+
354
+ .money-symbol {
355
+ color: #44ff44;
356
+ margin-right: 4px;
357
+ }
358
+
359
+ .health-bar {
360
+ width: 200px;
361
+ height: 20px;
362
+ background: rgba(0, 0, 0, 0.5);
363
+ border-radius: 3px;
364
+ overflow: hidden;
365
+ box-shadow: 0 0 10px rgba(255, 0, 0, 0.3);
366
+ position: relative;
367
+ }
368
+
369
+ .health-bar-fill {
370
+ width: 100%;
371
+ height: 100%;
372
+ background: linear-gradient(to right, #ff0000, #ff3333);
373
+ transition: width 0.3s ease;
374
+ }
375
+
376
+ .health-icon {
377
+ position: absolute;
378
+ left: 5px;
379
+ top: 50%;
380
+ transform: translateY(-50%);
381
+ height: 16px;
382
+ width: 16px;
383
+ z-index: 1;
384
+ }
385
+
386
+ .health-text {
387
+ position: absolute;
388
+ left: 50%;
389
+ top: 50%;
390
+ transform: translate(-50%, -50%);
391
+ font-size: 0.8em;
392
+ font-weight: bold;
393
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
394
+ z-index: 1;
395
+ }
396
+
397
+ .weapon-info {
398
+ display: flex;
399
+ align-items: center;
400
+ justify-content: flex-end;
401
+ gap: 10px;
402
+ }
403
+
404
+ .weapon-text {
405
+ text-align: right;
406
+ }
407
+
408
+ .weapon-name {
409
+ font-size: 1em;
410
+ font-weight: bold;
411
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
412
+ }
413
+
414
+ .weapon-ammo {
415
+ font-size: 0.8em;
416
+ opacity: 0.9;
417
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.5);
418
+ }
419
+
420
+ .weapon-image {
421
+ background-color: rgba(0, 0, 0, 0.5);
422
+ border-radius: 3px;
423
+ width: 50px;
424
+ height: 50px;
425
+ object-fit: contain;
426
+ }
427
+
428
+ .crosshair {
429
+ position: fixed;
430
+ top: 50%;
431
+ left: 50%;
432
+ transform: translate(-50%, -50%);
433
+ width: 20px;
434
+ height: 20px;
435
+ pointer-events: none;
436
+ opacity: 0.7;
437
+ }
438
+
439
+ .crosshair::before,
440
+ .crosshair::after {
441
+ content: '';
442
+ position: absolute;
443
+ background-color: rgba(255, 255, 255, 0.8);
444
+ }
445
+
446
+ .crosshair::before {
447
+ width: 2px;
448
+ height: 100%;
449
+ left: 50%;
450
+ transform: translateX(-50%);
451
+ }
452
+
453
+ .crosshair::after {
454
+ width: 100%;
455
+ height: 2px;
456
+ top: 50%;
457
+ transform: translateY(-50%);
458
+ }
459
+
460
+ .purchase-label {
461
+ background-color: rgba(0, 0, 0, 0.9);
462
+ padding: 12px 20px;
463
+ border-radius: 4px;
464
+ text-align: center;
465
+ color: #ffffff;
466
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
467
+ font-family: 'Arial', sans-serif;
468
+ text-transform: uppercase;
469
+ position: relative;
470
+ width: 220px;
471
+ margin: 0 auto;
472
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
473
+ }
474
+
475
+ .label-name {
476
+ font-size: 20px;
477
+ margin-bottom: 6px;
478
+ font-weight: bold;
479
+ }
480
+
481
+ .label-cost {
482
+ font-size: 16px;
483
+ margin-bottom: 6px;
484
+ color: #44ff44;
485
+ }
486
+
487
+ .label-prompt {
488
+ font-size: 14px;
489
+ opacity: 0.8;
490
+ }
491
+
492
+ .label-caret {
493
+ position: absolute;
494
+ bottom: -8px;
495
+ left: 50%;
496
+ transform: translateX(-50%);
497
+ width: 0;
498
+ height: 0;
499
+ border-left: 8px solid transparent;
500
+ border-right: 8px solid transparent;
501
+ border-top: 8px solid rgba(0, 0, 0, 0.7);
502
+ }
503
+
504
+ .weapon-roulette {
505
+ background-color: rgba(0, 0, 0, 0.9);
506
+ padding: 20px;
507
+ border-radius: 4px;
508
+ text-align: center;
509
+ position: relative;
510
+ margin: 0 auto;
511
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
512
+ color: #ffffff;
513
+ font-family: 'Arial', sans-serif;
514
+ text-transform: uppercase;
515
+ }
516
+
517
+ .roulette-image {
518
+ width: 100px;
519
+ height: 100px;
520
+ object-fit: contain;
521
+ background-color: rgba(255, 255, 255, 0.1);
522
+ border-radius: 3px;
523
+ padding: 10px;
524
+ margin-bottom: 10px;
525
+ }
526
+
527
+ .roulette-name {
528
+ font-size: 16px;
529
+ font-weight: bold;
530
+ margin-bottom: 8px;
531
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
532
+ }
533
+
534
+ .roulette-prompt {
535
+ font-size: 12px;
536
+ opacity: 0.8;
537
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
538
+ }
539
+
540
+ .downed-player {
541
+ background-color: rgba(0, 0, 0, 0.9);
542
+ padding: 12px 20px;
543
+ border-radius: 4px;
544
+ text-align: center;
545
+ color: #ffffff;
546
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
547
+ font-family: 'Arial', sans-serif;
548
+ text-transform: uppercase;
549
+ width: 220px;
550
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.2);
551
+ position: relative;
552
+ }
553
+
554
+ .revive-text {
555
+ font-size: 16px;
556
+ margin-bottom: 8px;
557
+ font-weight: bold;
558
+ color: #ff4444;
559
+ }
560
+
561
+ .revive-prompt {
562
+ font-size: 14px;
563
+ opacity: 0.8;
564
+ text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
565
+ margin-bottom: 8px;
566
+ }
567
+
568
+ .revive-progress {
569
+ width: 100%;
570
+ height: 6px;
571
+ background: rgba(255, 255, 255, 0.2);
572
+ border-radius: 3px;
573
+ overflow: hidden;
574
+ margin-bottom: 8px;
575
+ }
576
+
577
+ .revive-progress-fill {
578
+ width: 0%;
579
+ height: 100%;
580
+ background: linear-gradient(to right, #44ff44, #88ff88);
581
+ transition: width 0.3s ease;
582
+ }
583
+
584
+ .wave-announcement {
585
+ position: fixed;
586
+ top: 50%;
587
+ left: 50%;
588
+ transform: translate(-50%, -50%) scale(0);
589
+ font-family: "Creepster", serif;
590
+ font-weight: 400;
591
+ font-style: normal; font-size: 72px;
592
+ color: #ff0000;
593
+ text-shadow: 0 0 20px rgba(255, 0, 0, 0.8);
594
+ opacity: 0;
595
+ transition: transform 0.5s ease, opacity 0.5s ease;
596
+ pointer-events: none;
597
+ z-index: 1000;
598
+ }
599
+
600
+ .wave-announcement.show {
601
+ transform: translate(-50%, -50%) scale(1);
602
+ opacity: 1;
603
+ animation: pulseAndFade 3s ease-out forwards;
604
+ }
605
+
606
+ @keyframes pulseAndFade {
607
+ 0% {
608
+ transform: translate(-50%, -50%) scale(0);
609
+ opacity: 0;
610
+ }
611
+ 20% {
612
+ transform: translate(-50%, -50%) scale(1.2);
613
+ opacity: 1;
614
+ }
615
+ 40% {
616
+ transform: translate(-50%, -50%) scale(1);
617
+ opacity: 1;
618
+ }
619
+ 80% {
620
+ transform: translate(-50%, -50%) scale(1);
621
+ opacity: 1;
622
+ }
623
+ 100% {
624
+ transform: translate(-50%, -50%) scale(0.8);
625
+ opacity: 0;
626
+ }
627
+ }
628
+ </style>