@hytopia.com/examples 1.0.52 → 1.0.54

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.
@@ -119,6 +119,7 @@
119
119
  <div id="mobile-attack-button" class="mobile-button">
120
120
  <img src="{{CDN_ASSETS_URL}}/icons/mobile-shoot.png" />
121
121
  </div>
122
+ <div class="mobile-auto-fire-label">Aim gun at target to auto-fire</div>
122
123
 
123
124
  <div id="mobile-place-block-button" class="mobile-button">
124
125
  <img src="{{CDN_ASSETS_URL}}/icons/mobile-place-block.png" />
@@ -214,6 +215,12 @@
214
215
  const summarySelfNameEl = document.querySelector('.summary-self-name');
215
216
  const summarySelfKillsEl = document.querySelector('.summary-self-kills');
216
217
 
218
+ const isMobileClient = Boolean(hytopia?.isMobile);
219
+ hytopia.sendData({
220
+ type: 'client-platform',
221
+ isMobile: isMobileClient,
222
+ });
223
+
217
224
  function updateLeaderboard() {
218
225
  const leaderboardPlayers = document.querySelector('.leaderboard-players');
219
226
  leaderboardPlayers.innerHTML = '';
@@ -1547,6 +1554,10 @@
1547
1554
  display: none;
1548
1555
  }
1549
1556
 
1557
+ .mobile-auto-fire-label {
1558
+ display: none;
1559
+ }
1560
+
1550
1561
  .leaderboard-mobile-summary {
1551
1562
  display: none;
1552
1563
  gap: 4px;
@@ -1727,8 +1738,8 @@
1727
1738
  body.mobile .mobile-buttons-container {
1728
1739
  display: block;
1729
1740
  position: fixed;
1730
- bottom: 30px;
1731
- right: 50px;
1741
+ bottom: 60px;
1742
+ right: 80px;
1732
1743
  width: 250px;
1733
1744
  height: 230px;
1734
1745
  user-select: none;
@@ -1759,6 +1770,21 @@
1759
1770
  pointer-events: auto !important;
1760
1771
  }
1761
1772
 
1773
+ body.mobile .mobile-auto-fire-label {
1774
+ position: absolute;
1775
+ bottom: -45px;
1776
+ right: -60px;
1777
+ font-family: 'Inter', sans-serif;
1778
+ font-size: 11px;
1779
+ font-weight: 600;
1780
+ color: rgba(255, 255, 255, 0.85);
1781
+ text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
1782
+ letter-spacing: 0.5px;
1783
+ pointer-events: none;
1784
+ display: block;
1785
+ text-transform: uppercase;
1786
+ }
1787
+
1762
1788
  body.mobile .mobile-button.active {
1763
1789
  transform: scale(0.92);
1764
1790
  background-color: rgba(0, 0, 0, 0.75);
@@ -26,6 +26,7 @@ const BASE_HEALTH = 100;
26
26
  const BASE_SHIELD = 0;
27
27
  const BLOCK_MATERIAL_COST = 3;
28
28
  const INTERACT_RANGE = 4;
29
+ const MOBILE_AUTOFIRE_SAMPLE_INTERVAL_TICKS = 3;
29
30
  const MAX_HEALTH = 100;
30
31
  const MAX_SHIELD = 100;
31
32
  const TOTAL_INVENTORY_SLOTS = 6;
@@ -44,6 +45,10 @@ interface PlayerPersistedData extends Record<string, unknown> {
44
45
  export default class GamePlayerEntity extends DefaultPlayerEntity {
45
46
  private readonly _damageAudio: Audio;
46
47
  private readonly _inventory: (ItemEntity | undefined)[] = new Array(TOTAL_INVENTORY_SLOTS).fill(undefined);
48
+ private _isMobileClient: boolean = false;
49
+ private _autoFireRaycastCooldown: number = 0;
50
+ private _autoFireHasTarget: boolean = false;
51
+ private _autoFireEngaged: boolean = false;
47
52
  private _dead: boolean = false;
48
53
  private _health: number = BASE_HEALTH;
49
54
  private _inventoryActiveSlotIndex: number = 0;
@@ -163,6 +168,8 @@ export default class GamePlayerEntity extends DefaultPlayerEntity {
163
168
  public checkDeath(attacker?: GamePlayerEntity): void {
164
169
  if (this.health <= 0) {
165
170
  this._dead = true;
171
+ this._autoFireRaycastCooldown = 0;
172
+ this._autoFireHasTarget = false;
166
173
 
167
174
  if (attacker) {
168
175
  GameManager.instance.addKill(attacker.player.username);
@@ -289,6 +296,8 @@ export default class GamePlayerEntity extends DefaultPlayerEntity {
289
296
  if (!this.world) return;
290
297
 
291
298
  this._dead = false;
299
+ this._autoFireRaycastCooldown = 0;
300
+ this._autoFireHasTarget = false;
292
301
  this.health = this._maxHealth;
293
302
  this.shield = 0;
294
303
  this.resetAnimations();
@@ -420,6 +429,11 @@ export default class GamePlayerEntity extends DefaultPlayerEntity {
420
429
  this.player.ui.on(PlayerUIEvent.DATA, (payload) => {
421
430
  const { data } = payload;
422
431
 
432
+ if (data.type === 'client-platform') {
433
+ this._isMobileClient = Boolean(data.isMobile);
434
+ return;
435
+ }
436
+
423
437
  if (data.type === 'inventory-select') {
424
438
  this.setActiveInventorySlotIndex(data.index);
425
439
  }
@@ -439,6 +453,8 @@ export default class GamePlayerEntity extends DefaultPlayerEntity {
439
453
  return;
440
454
  }
441
455
 
456
+ this._applyMobileAutoFire(input);
457
+
442
458
  if (input.ml) {
443
459
  this._handleMouseLeftClick();
444
460
  }
@@ -470,6 +486,74 @@ export default class GamePlayerEntity extends DefaultPlayerEntity {
470
486
  this._handleInventoryHotkeys(input);
471
487
  }
472
488
 
489
+ private _applyMobileAutoFire(input: EventPayloads[BaseEntityControllerEvent.TICK_WITH_PLAYER_INPUT]['input']): void {
490
+ const wasAutoEngaged = this._autoFireEngaged;
491
+ const manualRequested = input.ml && !wasAutoEngaged;
492
+ this._autoFireEngaged = false;
493
+
494
+ const disableAutoFire = () => {
495
+ this._autoFireHasTarget = false;
496
+ this._autoFireRaycastCooldown = 0;
497
+ };
498
+
499
+ const activeItem = this._inventory[this._inventoryActiveSlotIndex];
500
+ const canUseAutoFire = this._isMobileClient && this.world && activeItem instanceof GunEntity;
501
+
502
+ if (!canUseAutoFire) {
503
+ disableAutoFire();
504
+ } else if (this._autoFireRaycastCooldown <= 0) {
505
+ this._autoFireHasTarget = this._hasTargetInCrosshair(activeItem);
506
+ this._autoFireRaycastCooldown = MOBILE_AUTOFIRE_SAMPLE_INTERVAL_TICKS;
507
+ } else {
508
+ this._autoFireRaycastCooldown--;
509
+ }
510
+
511
+ if (this._autoFireHasTarget) {
512
+ input.ml = true;
513
+ this._autoFireEngaged = true;
514
+ return;
515
+ }
516
+
517
+ if (manualRequested) {
518
+ input.ml = true;
519
+ return;
520
+ }
521
+
522
+ if (wasAutoEngaged) {
523
+ input.ml = false;
524
+ return;
525
+ }
526
+
527
+ input.ml = false;
528
+ }
529
+
530
+ private _hasTargetInCrosshair(activeGun: GunEntity): boolean {
531
+ if (!this.world) {
532
+ return false;
533
+ }
534
+
535
+ const origin = {
536
+ x: this.position.x,
537
+ y: this.position.y + this.player.camera.offset.y,
538
+ z: this.position.z,
539
+ };
540
+
541
+ const raycastHit = this.world.simulation.raycast(
542
+ origin,
543
+ this.player.camera.facingDirection,
544
+ activeGun.getEffectiveRange(),
545
+ {
546
+ filterExcludeRigidBody: this.rawRigidBody,
547
+ }
548
+ );
549
+
550
+ return Boolean(
551
+ raycastHit?.hitEntity instanceof GamePlayerEntity &&
552
+ raycastHit.hitEntity !== this &&
553
+ !raycastHit.hitEntity.isDead
554
+ );
555
+ }
556
+
473
557
  private _handleMouseLeftClick(): void {
474
558
  const activeItem = this._inventory[this._inventoryActiveSlotIndex];
475
559
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hytopia.com/examples",
3
- "version": "1.0.52",
3
+ "version": "1.0.54",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",