@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:
|
|
1731
|
-
right:
|
|
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
|
|