hytopia 0.3.7 → 0.3.9
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.
- package/docs/server.playerentitycontroller.md +1 -1
- package/docs/server.playerentitycontroller.tickwithplayerinput.md +1 -1
- package/examples/hygrounds/assets/icons/auto-sniper.png +0 -0
- package/examples/hygrounds/assets/icons/gravity-potion.png +0 -0
- package/examples/hygrounds/assets/icons/revolver.png +0 -0
- package/examples/hygrounds/assets/icons/submachine-gun.png +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper-named-nodes.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper.glb.md5 +1 -0
- package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion-named-nodes.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion.glb.md5 +1 -0
- package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver-named-nodes.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver.glb.md5 +1 -0
- package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun-named-nodes.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun.glb +0 -0
- package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun.glb.md5 +1 -0
- package/examples/hygrounds/assets/models/items/auto-sniper.glb +0 -0
- package/examples/hygrounds/assets/models/items/gravity-potion.glb +0 -0
- package/examples/hygrounds/assets/models/items/revolver.glb +0 -0
- package/examples/hygrounds/assets/models/items/submachine-gun.glb +0 -0
- package/examples/hygrounds/assets/ui/index.html +51 -1
- package/examples/hygrounds/classes/GameManager.ts +39 -1
- package/examples/hygrounds/classes/GamePlayerEntity.ts +44 -13
- package/examples/hygrounds/classes/GunEntity.ts +1 -5
- package/examples/hygrounds/classes/ItemFactory.ts +12 -0
- package/examples/hygrounds/classes/items/GravityPotionEntity.ts +48 -0
- package/examples/hygrounds/classes/items/ShieldPotionEntity.ts +1 -1
- package/examples/hygrounds/classes/weapons/AK47Entity.ts +1 -1
- package/examples/hygrounds/classes/weapons/AutoShotgunEntity.ts +2 -2
- package/examples/hygrounds/classes/weapons/AutoSniperEntity.ts +43 -0
- package/examples/hygrounds/classes/weapons/LightMachineGunEntity.ts +3 -3
- package/examples/hygrounds/classes/weapons/RevolverEntity.ts +46 -0
- package/examples/hygrounds/classes/weapons/RocketLauncherEntity.ts +11 -2
- package/examples/hygrounds/classes/weapons/ShotgunEntity.ts +1 -1
- package/examples/hygrounds/classes/weapons/SubmachineGunEntity.ts +43 -0
- package/examples/hygrounds/gameConfig.ts +38 -6
- package/examples/hygrounds/index.ts +2 -1
- package/examples/player-persistence/README.md +3 -0
- package/examples/player-persistence/assets/map.json +2623 -0
- package/examples/player-persistence/dev/persistence/player-player-1.json +6 -0
- package/examples/player-persistence/dev/persistence/test.json +3 -0
- package/examples/player-persistence/index.ts +126 -0
- package/examples/player-persistence/package.json +16 -0
- package/package.json +1 -1
- package/server.api.json +1 -1
- package/server.d.ts +3 -1
- package/server.js +1 -1
- /package/examples/hygrounds/assets/audio/sfx/{shield-potion-consume.mp3 → potion-consume.mp3} +0 -0
@@ -457,7 +457,7 @@ Called when the controlled entity is spawned. In PlayerEntityController, this fu
|
|
457
457
|
|
458
458
|
</td><td>
|
459
459
|
|
460
|
-
Ticks the player movement for the entity controller, overriding the default implementation.
|
460
|
+
Ticks the player movement for the entity controller, overriding the default implementation. If the entity to tick is a child entity, only the event will be emitted but the default movement logic will not be applied.
|
461
461
|
|
462
462
|
|
463
463
|
</td></tr>
|
@@ -4,7 +4,7 @@
|
|
4
4
|
|
5
5
|
## PlayerEntityController.tickWithPlayerInput() method
|
6
6
|
|
7
|
-
Ticks the player movement for the entity controller, overriding the default implementation.
|
7
|
+
Ticks the player movement for the entity controller, overriding the default implementation. If the entity to tick is a child entity, only the event will be emitted but the default movement logic will not be applied.
|
8
8
|
|
9
9
|
**Signature:**
|
10
10
|
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper-named-nodes.glb
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
eb57c9bd2558eab8508e66cebc58793ce1b660c3bc089533623b8ec937e6c33c
|
Binary file
|
Binary file
|
package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion.glb.md5
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
64e7588c50e1ca56989a396252683fb9e16cb8c5814237b46562983a3aa35fe4
|
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
e4d9e2e03c083b63c01b59dab34218c6aae28bbd88fcda7db44e2dcb2075a080
|
Binary file
|
Binary file
|
package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun.glb.md5
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
85850ccf234d28a1d2a306cc3b9dd7ec8196b759be5011e38a96ded3605e9201
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -20,6 +20,9 @@
|
|
20
20
|
<!-- Game Start Announcement -->
|
21
21
|
<div class="game-start-announcement">DEATHMATCH!</div>
|
22
22
|
|
23
|
+
<!-- Winner Announcement -->
|
24
|
+
<div class="winner-announcement"></div>
|
25
|
+
|
23
26
|
<!-- Leaderboard -->
|
24
27
|
<div class="leaderboard">
|
25
28
|
<div class="leaderboard-title">HyGrounds Deathmatch</div>
|
@@ -224,6 +227,17 @@
|
|
224
227
|
}, 3000);
|
225
228
|
}
|
226
229
|
|
230
|
+
function showWinnerAnnouncement(username) {
|
231
|
+
const announcement = document.querySelector('.winner-announcement');
|
232
|
+
announcement.textContent = `${username} wins!`;
|
233
|
+
announcement.classList.add('active');
|
234
|
+
|
235
|
+
// Remove the active class after animation completes
|
236
|
+
setTimeout(() => {
|
237
|
+
announcement.classList.remove('active');
|
238
|
+
}, 5000);
|
239
|
+
}
|
240
|
+
|
227
241
|
hytopia.registerSceneUITemplate('item-label', (id, onState) => {
|
228
242
|
const template = document.getElementById('item-label-template');
|
229
243
|
const clone = template.content.cloneNode(true);
|
@@ -268,6 +282,11 @@
|
|
268
282
|
showGameStartAnnouncement();
|
269
283
|
}
|
270
284
|
|
285
|
+
if (type === 'announce-winner') {
|
286
|
+
const { username } = data;
|
287
|
+
showWinnerAnnouncement(username);
|
288
|
+
}
|
289
|
+
|
271
290
|
if (type === 'ammo-indicator') {
|
272
291
|
const { ammo, totalAmmo, show, reloading } = data;
|
273
292
|
|
@@ -540,7 +559,29 @@
|
|
540
559
|
}
|
541
560
|
|
542
561
|
.game-start-announcement.active {
|
543
|
-
animation: announcementFade
|
562
|
+
animation: announcementFade 5s ease-in-out forwards;
|
563
|
+
}
|
564
|
+
|
565
|
+
.winner-announcement {
|
566
|
+
position: fixed;
|
567
|
+
top: 50%;
|
568
|
+
left: 50%;
|
569
|
+
transform: translate(-50%, -50%);
|
570
|
+
font-family: 'Inter', sans-serif;
|
571
|
+
font-size: 80px;
|
572
|
+
font-weight: bold;
|
573
|
+
color: #ffd700;
|
574
|
+
text-shadow: 0 0 15px rgba(0, 0, 0, 0.8), 0 0 30px rgba(255, 215, 0, 0.6);
|
575
|
+
text-transform: uppercase;
|
576
|
+
letter-spacing: 3px;
|
577
|
+
opacity: 0;
|
578
|
+
z-index: 1000;
|
579
|
+
pointer-events: none;
|
580
|
+
text-align: center;
|
581
|
+
}
|
582
|
+
|
583
|
+
.winner-announcement.active {
|
584
|
+
animation: winnerFade 5s ease-in-out forwards;
|
544
585
|
}
|
545
586
|
|
546
587
|
@keyframes announcementFade {
|
@@ -550,6 +591,15 @@
|
|
550
591
|
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
551
592
|
}
|
552
593
|
|
594
|
+
@keyframes winnerFade {
|
595
|
+
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
|
596
|
+
20% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
|
597
|
+
40% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); }
|
598
|
+
60% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
|
599
|
+
80% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
600
|
+
100% { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
|
601
|
+
}
|
602
|
+
|
553
603
|
.hit-damage-container {
|
554
604
|
position: fixed;
|
555
605
|
top: 50%;
|
@@ -96,6 +96,8 @@ export default class GameManager {
|
|
96
96
|
this._gameActive = false;
|
97
97
|
this.world.chatManager.sendBroadcastMessage('Game over! Starting the next round in 10 seconds...', 'FF0000');
|
98
98
|
|
99
|
+
this._focusWinningPlayer();
|
100
|
+
|
99
101
|
// Clear any existing restart timer
|
100
102
|
if (this._restartTimer) {
|
101
103
|
clearTimeout(this._restartTimer);
|
@@ -205,6 +207,7 @@ export default class GameManager {
|
|
205
207
|
if (playerEntity instanceof GamePlayerEntity) {
|
206
208
|
playerEntity.setActiveInventorySlotIndex(0); // reset to pickaxe at slot 0
|
207
209
|
playerEntity.dropAllInventoryItems();
|
210
|
+
playerEntity.resetCamera();
|
208
211
|
playerEntity.resetMaterials();
|
209
212
|
playerEntity.health = 100;
|
210
213
|
playerEntity.shield = 0;
|
@@ -239,6 +242,41 @@ export default class GameManager {
|
|
239
242
|
this.resetLeaderboard();
|
240
243
|
}
|
241
244
|
|
245
|
+
public _focusWinningPlayer() {
|
246
|
+
if (!this.world) return;
|
247
|
+
|
248
|
+
// Find player with most kills
|
249
|
+
let highestKills = 0;
|
250
|
+
let winningPlayer = '';
|
251
|
+
|
252
|
+
this._killCounter.forEach((kills, player) => {
|
253
|
+
if (kills > highestKills) {
|
254
|
+
highestKills = kills;
|
255
|
+
winningPlayer = player;
|
256
|
+
}
|
257
|
+
});
|
258
|
+
|
259
|
+
// Get winning player entity
|
260
|
+
const winningPlayerEntity = this.world.entityManager
|
261
|
+
.getAllPlayerEntities()
|
262
|
+
.find(entity => entity.player.username === winningPlayer);
|
263
|
+
|
264
|
+
if (!winningPlayerEntity) return;
|
265
|
+
|
266
|
+
this.world.entityManager.getAllPlayerEntities().forEach(playerEntity => {
|
267
|
+
if (playerEntity instanceof GamePlayerEntity) {
|
268
|
+
if (playerEntity.player.username !== winningPlayer) { // don't change camera for the winner
|
269
|
+
playerEntity.focusCameraOnPlayer(winningPlayerEntity as GamePlayerEntity);
|
270
|
+
}
|
271
|
+
|
272
|
+
playerEntity.player.ui.sendData({
|
273
|
+
type: 'announce-winner',
|
274
|
+
username: winningPlayer,
|
275
|
+
});
|
276
|
+
}
|
277
|
+
});
|
278
|
+
}
|
279
|
+
|
242
280
|
/**
|
243
281
|
* Syncs UI for all connected players
|
244
282
|
*/
|
@@ -262,7 +300,7 @@ export default class GameManager {
|
|
262
300
|
this.world.chatManager.sendPlayerMessage(player, '- Search for chests and weapons to survive');
|
263
301
|
this.world.chatManager.sendPlayerMessage(player, '- Break blocks with your pickaxe to gain materials');
|
264
302
|
this.world.chatManager.sendPlayerMessage(player, '- Right click to spend 3 materials to place a block');
|
265
|
-
this.world.chatManager.sendPlayerMessage(player, '- Some weapons
|
303
|
+
this.world.chatManager.sendPlayerMessage(player, '- Some weapons zoom with "Z". Drop items with "Q"');
|
266
304
|
}
|
267
305
|
|
268
306
|
/**
|
@@ -92,6 +92,7 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
92
92
|
super.spawn(world, position, rotation);
|
93
93
|
this._setupPlayerInventory();
|
94
94
|
this._autoHealTicker();
|
95
|
+
this._outOfWorldTicker();
|
95
96
|
this._updatePlayerUIHealth();
|
96
97
|
}
|
97
98
|
|
@@ -121,11 +122,7 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
121
122
|
|
122
123
|
if (attacker) {
|
123
124
|
GameManager.instance.addKill(attacker.player.username);
|
124
|
-
|
125
|
-
// Focus on the player that killed you
|
126
|
-
this.player.camera.setMode(PlayerCameraMode.THIRD_PERSON);
|
127
|
-
this.player.camera.setAttachedToEntity(attacker);
|
128
|
-
this.player.camera.setModelHiddenNodes([]);
|
125
|
+
this.focusCameraOnPlayer(attacker);
|
129
126
|
}
|
130
127
|
|
131
128
|
this.dropAllInventoryItems();
|
@@ -137,16 +134,26 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
137
134
|
});
|
138
135
|
|
139
136
|
this.playerController.idleLoopedAnimations = [ 'sleep' ];
|
140
|
-
this.world.chatManager.sendPlayerMessage(this.player, 'You have died! Respawning in
|
141
|
-
this._respawnTimer = setTimeout(() => this.respawn(),
|
137
|
+
this.world.chatManager.sendPlayerMessage(this.player, 'You have died! Respawning in 5 seconds...', 'FF0000');
|
138
|
+
this._respawnTimer = setTimeout(() => this.respawn(), 5 * 1000);
|
142
139
|
|
143
140
|
if (attacker) {
|
144
|
-
this.
|
141
|
+
if (this.player.username !== attacker.player.username) {
|
142
|
+
this.world.chatManager.sendBroadcastMessage(`${attacker.player.username} killed ${this.player.username} with a ${attacker.getActiveItemName()}!`, 'FF0000');
|
143
|
+
} else {
|
144
|
+
this.world.chatManager.sendBroadcastMessage(`${this.player.username} self-destructed!`, 'FF0000');
|
145
|
+
}
|
145
146
|
}
|
146
147
|
}
|
147
148
|
}
|
148
149
|
}
|
149
150
|
|
151
|
+
public focusCameraOnPlayer(player: GamePlayerEntity): void {
|
152
|
+
this.player.camera.setMode(PlayerCameraMode.THIRD_PERSON);
|
153
|
+
this.player.camera.setAttachedToEntity(player);
|
154
|
+
this.player.camera.setModelHiddenNodes([]);
|
155
|
+
}
|
156
|
+
|
150
157
|
public dealtDamage(damage: number): void {
|
151
158
|
this.player.ui.sendData({
|
152
159
|
type: 'show-damage',
|
@@ -206,6 +213,11 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
206
213
|
this.playerController.runLoopedAnimations = ['run_lower', 'run_upper'];
|
207
214
|
}
|
208
215
|
|
216
|
+
public resetCamera(): void {
|
217
|
+
this._setupPlayerCamera();
|
218
|
+
this.player.camera.setAttachedToEntity(this);
|
219
|
+
}
|
220
|
+
|
209
221
|
public resetMaterials(): void {
|
210
222
|
this._materials = 0;
|
211
223
|
this._updatePlayerUIMaterials();
|
@@ -238,8 +250,12 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
238
250
|
this._updatePlayerUIInventoryActiveSlot();
|
239
251
|
}
|
240
252
|
|
253
|
+
public setGravity(gravityScale: number): void {
|
254
|
+
this.setGravityScale(gravityScale);
|
255
|
+
}
|
256
|
+
|
241
257
|
public takeDamage(damage: number, hitDirection: Vector3Like, attacker?: GamePlayerEntity): void {
|
242
|
-
if (!this.isSpawned || !this.world || !GameManager.instance.isGameActive ||
|
258
|
+
if (!this.isSpawned || !this.world || !GameManager.instance.isGameActive || this._dead) return;
|
243
259
|
|
244
260
|
this._playDamageAudio();
|
245
261
|
|
@@ -331,7 +347,7 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
331
347
|
|
332
348
|
private _onTickWithPlayerInput = (payload: EventPayloads[BaseEntityControllerEvent.TICK_WITH_PLAYER_INPUT]): void => {
|
333
349
|
const { input } = payload;
|
334
|
-
|
350
|
+
|
335
351
|
if (this._dead) {
|
336
352
|
return;
|
337
353
|
}
|
@@ -459,7 +475,10 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
459
475
|
origin,
|
460
476
|
this.player.camera.facingDirection,
|
461
477
|
INTERACT_RANGE,
|
462
|
-
{
|
478
|
+
{
|
479
|
+
filterExcludeRigidBody: this.rawRigidBody,
|
480
|
+
filterFlags: 8, // Rapier exclude sensors,
|
481
|
+
}
|
463
482
|
);
|
464
483
|
|
465
484
|
const hitEntity = raycastHit?.hitEntity;
|
@@ -552,13 +571,25 @@ export default class GamePlayerEntity extends PlayerEntity {
|
|
552
571
|
|
553
572
|
private _autoHealTicker(): void {
|
554
573
|
setTimeout(() => {
|
555
|
-
if (!this.isSpawned
|
574
|
+
if (!this.isSpawned) return;
|
556
575
|
|
557
|
-
if (this.health < this._maxHealth) {
|
576
|
+
if (this.health < this._maxHealth && !this._dead) {
|
558
577
|
this.health += 1;
|
559
578
|
}
|
560
579
|
|
561
580
|
this._autoHealTicker();
|
562
581
|
}, 2000);
|
563
582
|
}
|
583
|
+
|
584
|
+
private _outOfWorldTicker(): void {
|
585
|
+
setTimeout(() => {
|
586
|
+
if (!this.isSpawned) return;
|
587
|
+
|
588
|
+
if (this.position.y < -100 && !this._dead) {
|
589
|
+
this.takeDamage(MAX_HEALTH + MAX_SHIELD, { x: 0, y: 0, z: -1 });
|
590
|
+
}
|
591
|
+
|
592
|
+
this._outOfWorldTicker();
|
593
|
+
}, 3000);
|
594
|
+
}
|
564
595
|
}
|
@@ -142,11 +142,7 @@ export default abstract class GunEntity extends ItemEntity {
|
|
142
142
|
const direction = player.player.camera.facingDirection;
|
143
143
|
|
144
144
|
return {
|
145
|
-
origin: {
|
146
|
-
x: x + (direction.x * 0.5),
|
147
|
-
y: y + (direction.y * 0.5) + cameraYOffset,
|
148
|
-
z: z + (direction.z * 0.5),
|
149
|
-
},
|
145
|
+
origin: { x, y: y + cameraYOffset, z },
|
150
146
|
direction
|
151
147
|
};
|
152
148
|
}
|
@@ -13,9 +13,15 @@ export default class ItemFactory {
|
|
13
13
|
case 'auto-shotgun':
|
14
14
|
itemModule = await import('./weapons/AutoShotgunEntity');
|
15
15
|
break;
|
16
|
+
case 'auto-sniper':
|
17
|
+
itemModule = await import('./weapons/AutoSniperEntity');
|
18
|
+
break;
|
16
19
|
case 'bolt-action-sniper':
|
17
20
|
itemModule = await import('./weapons/BoltActionSniperEntity');
|
18
21
|
break;
|
22
|
+
case 'gravity-potion':
|
23
|
+
itemModule = await import('./items/GravityPotionEntity');
|
24
|
+
break;
|
19
25
|
case 'light-machine-gun':
|
20
26
|
itemModule = await import('./weapons/LightMachineGunEntity');
|
21
27
|
break;
|
@@ -28,9 +34,15 @@ export default class ItemFactory {
|
|
28
34
|
case 'pistol':
|
29
35
|
itemModule = await import('./weapons/PistolEntity');
|
30
36
|
break;
|
37
|
+
case 'revolver':
|
38
|
+
itemModule = await import('./weapons/RevolverEntity');
|
39
|
+
break;
|
31
40
|
case 'rocket-launcher':
|
32
41
|
itemModule = await import('./weapons/RocketLauncherEntity');
|
33
42
|
break;
|
43
|
+
case 'submachine-gun':
|
44
|
+
itemModule = await import('./weapons/SubmachineGunEntity');
|
45
|
+
break;
|
34
46
|
case 'shotgun':
|
35
47
|
itemModule = await import('./weapons/ShotgunEntity');
|
36
48
|
break;
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { Quaternion } from 'hytopia';
|
2
|
+
import ItemEntity from "../ItemEntity";
|
3
|
+
import GamePlayerEntity from '../GamePlayerEntity';
|
4
|
+
import type { ItemEntityOptions } from "../ItemEntity";
|
5
|
+
|
6
|
+
const GRAVITY_SCALE = 0.3;
|
7
|
+
const GRAVITY_DURATION_MS = 15 * 1000; // 15 seconds
|
8
|
+
|
9
|
+
const DEFAULT_GRAVITY_POTION_OPTIONS: ItemEntityOptions = {
|
10
|
+
heldHand: 'right',
|
11
|
+
iconImageUri: 'icons/gravity-potion.png',
|
12
|
+
idleAnimation: 'idle_gun_right',
|
13
|
+
mlAnimation: 'shoot_gun_right',
|
14
|
+
modelUri: 'models/items/gravity-potion.glb',
|
15
|
+
modelScale: 0.4,
|
16
|
+
name: 'Gravity Potion',
|
17
|
+
consumable: true,
|
18
|
+
consumeAudioUri: 'audio/sfx/potion-consume.mp3',
|
19
|
+
consumeTimeMs: 1000,
|
20
|
+
quantity: 1,
|
21
|
+
}
|
22
|
+
|
23
|
+
export default class GravityPotionEntity extends ItemEntity {
|
24
|
+
public constructor(options: Partial<ItemEntityOptions> = {}) {
|
25
|
+
super({ ...DEFAULT_GRAVITY_POTION_OPTIONS, ...options });
|
26
|
+
}
|
27
|
+
|
28
|
+
public override consume(): void {
|
29
|
+
if (!(this.parent instanceof GamePlayerEntity)) {
|
30
|
+
return;
|
31
|
+
}
|
32
|
+
|
33
|
+
const parent = this.parent as GamePlayerEntity;
|
34
|
+
|
35
|
+
// Apply gravity potion effect
|
36
|
+
parent.setGravity(GRAVITY_SCALE);
|
37
|
+
setTimeout(() => parent.setGravity(1), GRAVITY_DURATION_MS);
|
38
|
+
|
39
|
+
super.consume();
|
40
|
+
}
|
41
|
+
|
42
|
+
public override equip(): void {
|
43
|
+
super.equip();
|
44
|
+
|
45
|
+
this.setPosition({ x: 0, y: 0.15, z: -0.2 });
|
46
|
+
this.setRotation(Quaternion.fromEuler(-90, 0, 0));
|
47
|
+
}
|
48
|
+
}
|
@@ -14,7 +14,7 @@ const DEFAULT_SHIELD_POTION_OPTIONS: ItemEntityOptions = {
|
|
14
14
|
modelScale: 0.4,
|
15
15
|
name: 'Shield Potion',
|
16
16
|
consumable: true,
|
17
|
-
consumeAudioUri: 'audio/sfx/
|
17
|
+
consumeAudioUri: 'audio/sfx/potion-consume.mp3',
|
18
18
|
consumeTimeMs: 1000,
|
19
19
|
quantity: 1,
|
20
20
|
}
|
@@ -16,7 +16,7 @@ const DEFAULT_AK47_OPTIONS: GunEntityOptions = {
|
|
16
16
|
scopeZoom: 2,
|
17
17
|
modelUri: 'models/items/ak-47.glb',
|
18
18
|
modelScale: 1.3,
|
19
|
-
range:
|
19
|
+
range: 80,
|
20
20
|
reloadAudioUri: 'audio/sfx/rifle-reload.mp3',
|
21
21
|
reloadTimeMs: 2200,
|
22
22
|
shootAudioUri: 'audio/sfx/rifle-shoot.mp3',
|
@@ -4,7 +4,7 @@ import type { GunEntityOptions } from '../GunEntity';
|
|
4
4
|
|
5
5
|
const DEFAULT_AUTO_SHOTGUN_OPTIONS: GunEntityOptions = {
|
6
6
|
ammo: 6,
|
7
|
-
damage:
|
7
|
+
damage: 11,
|
8
8
|
fireRate: 1.5,
|
9
9
|
heldHand: 'both',
|
10
10
|
iconImageUri: 'icons/auto-shotgun.png',
|
@@ -15,7 +15,7 @@ const DEFAULT_AUTO_SHOTGUN_OPTIONS: GunEntityOptions = {
|
|
15
15
|
totalAmmo: 30,
|
16
16
|
modelUri: 'models/items/auto-shotgun.glb',
|
17
17
|
modelScale: 1.2,
|
18
|
-
range:
|
18
|
+
range: 10,
|
19
19
|
reloadAudioUri: 'audio/sfx/shotgun-reload.mp3',
|
20
20
|
reloadTimeMs: 3500,
|
21
21
|
shootAudioUri: 'audio/sfx/shotgun-shoot.mp3',
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { Quaternion, Vector3Like, QuaternionLike } from 'hytopia';
|
2
|
+
import GunEntity from '../GunEntity';
|
3
|
+
import type { GunEntityOptions } from '../GunEntity';
|
4
|
+
|
5
|
+
const DEFAULT_AUTO_SNIPER_OPTIONS: GunEntityOptions = {
|
6
|
+
ammo: 10,
|
7
|
+
damage: 50,
|
8
|
+
fireRate: 1.5,
|
9
|
+
heldHand: 'both',
|
10
|
+
iconImageUri: 'icons/auto-sniper.png',
|
11
|
+
idleAnimation: 'idle_gun_both',
|
12
|
+
mlAnimation: 'shoot_gun_both',
|
13
|
+
name: 'Auto Sniper',
|
14
|
+
maxAmmo: 10,
|
15
|
+
totalAmmo: 20,
|
16
|
+
scopeZoom: 5,
|
17
|
+
modelUri: 'models/items/auto-sniper.glb',
|
18
|
+
modelScale: 1.3,
|
19
|
+
range: 100,
|
20
|
+
reloadAudioUri: 'audio/sfx/sniper-reload.mp3',
|
21
|
+
reloadTimeMs: 2200,
|
22
|
+
shootAudioUri: 'audio/sfx/sniper-shoot.mp3',
|
23
|
+
};
|
24
|
+
|
25
|
+
export default class AutoSniperEntity extends GunEntity {
|
26
|
+
public constructor(options: Partial<GunEntityOptions> = {}) {
|
27
|
+
super({ ...DEFAULT_AUTO_SNIPER_OPTIONS, ...options });
|
28
|
+
}
|
29
|
+
|
30
|
+
public override shoot(): void {
|
31
|
+
if (!this.parent || !this.processShoot()) return;
|
32
|
+
|
33
|
+
super.shoot();
|
34
|
+
}
|
35
|
+
|
36
|
+
public override getMuzzleFlashPositionRotation(): { position: Vector3Like, rotation: QuaternionLike } {
|
37
|
+
return {
|
38
|
+
position: { x: 0, y: 0.01, z: -2.7 },
|
39
|
+
rotation: Quaternion.fromEuler(0, 90, 0),
|
40
|
+
};
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
@@ -4,7 +4,7 @@ import type { GunEntityOptions } from '../GunEntity';
|
|
4
4
|
|
5
5
|
const DEFAULT_LIGHT_MACHINE_GUN_OPTIONS: GunEntityOptions = {
|
6
6
|
ammo: 50,
|
7
|
-
damage:
|
7
|
+
damage: 9,
|
8
8
|
fireRate: 10,
|
9
9
|
heldHand: 'both',
|
10
10
|
iconImageUri: 'icons/light-machine-gun.png',
|
@@ -13,10 +13,10 @@ const DEFAULT_LIGHT_MACHINE_GUN_OPTIONS: GunEntityOptions = {
|
|
13
13
|
name: 'Light Machine Gun',
|
14
14
|
maxAmmo: 50,
|
15
15
|
totalAmmo: 300,
|
16
|
-
scopeZoom: 1.
|
16
|
+
scopeZoom: 1.35,
|
17
17
|
modelUri: 'models/items/light-machine-gun.glb',
|
18
18
|
modelScale: 1.3,
|
19
|
-
range:
|
19
|
+
range: 60,
|
20
20
|
reloadAudioUri: 'audio/sfx/machine-gun-reload.mp3',
|
21
21
|
reloadTimeMs: 4200,
|
22
22
|
shootAudioUri: 'audio/sfx/machine-gun-shoot.mp3',
|
@@ -0,0 +1,46 @@
|
|
1
|
+
import { Quaternion, Vector3Like, QuaternionLike } from 'hytopia';
|
2
|
+
import GunEntity from '../GunEntity';
|
3
|
+
import type { GunEntityOptions } from '../GunEntity';
|
4
|
+
import type GamePlayerEntity from '../GamePlayerEntity';
|
5
|
+
|
6
|
+
const DEFAULT_REVOLVER_OPTIONS: GunEntityOptions = {
|
7
|
+
ammo: 6,
|
8
|
+
damage: 45,
|
9
|
+
fireRate: 2,
|
10
|
+
heldHand: 'right',
|
11
|
+
iconImageUri: 'icons/revolver.png',
|
12
|
+
idleAnimation: 'idle_gun_right',
|
13
|
+
mlAnimation: 'shoot_gun_right',
|
14
|
+
name: 'Revolver',
|
15
|
+
maxAmmo: 6,
|
16
|
+
totalAmmo: 24,
|
17
|
+
modelUri: 'models/items/revolver.glb',
|
18
|
+
modelScale: 1.3,
|
19
|
+
range: 30,
|
20
|
+
reloadAudioUri: 'audio/sfx/pistol-reload.mp3',
|
21
|
+
reloadTimeMs: 2000,
|
22
|
+
shootAudioUri: 'audio/sfx/rifle-shoot.mp3',
|
23
|
+
};
|
24
|
+
|
25
|
+
export default class RevolverEntity extends GunEntity {
|
26
|
+
public constructor(options: Partial<GunEntityOptions> = {}) {
|
27
|
+
super({ ...DEFAULT_REVOLVER_OPTIONS, ...options });
|
28
|
+
}
|
29
|
+
|
30
|
+
public override shoot(): void {
|
31
|
+
if (!this.parent || !this.processShoot()) return;
|
32
|
+
|
33
|
+
super.shoot();
|
34
|
+
|
35
|
+
// Cancel input since pistol requires click-to-shoot
|
36
|
+
(this.parent as GamePlayerEntity).player.input.ml = false;
|
37
|
+
}
|
38
|
+
|
39
|
+
public override getMuzzleFlashPositionRotation(): { position: Vector3Like, rotation: QuaternionLike } {
|
40
|
+
return {
|
41
|
+
position: { x: 0.03, y: 0.18, z: -0.7 },
|
42
|
+
rotation: Quaternion.fromEuler(0, 90, 0),
|
43
|
+
};
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Audio, Entity, Quaternion, Vector3Like, QuaternionLike, RigidBodyType, EntityEvent, Vector3 } from 'hytopia';
|
1
|
+
import { Audio, CollisionGroup, Entity, Quaternion, Vector3Like, QuaternionLike, RigidBodyType, EntityEvent, Vector3, Collider } from 'hytopia';
|
2
2
|
import GunEntity from '../GunEntity';
|
3
3
|
import { BEDROCK_BLOCK_ID } from '../../gameConfig';
|
4
4
|
import type { GunEntityOptions } from '../GunEntity';
|
@@ -64,6 +64,15 @@ export default class RocketLauncherEntity extends GunEntity {
|
|
64
64
|
modelScale: 0.75,
|
65
65
|
rigidBodyOptions: {
|
66
66
|
type: RigidBodyType.KINEMATIC_VELOCITY,
|
67
|
+
colliders: [
|
68
|
+
{
|
69
|
+
...Collider.optionsFromModelUri('models/items/rocket-missile.glb', 0.75),
|
70
|
+
collisionGroups: {
|
71
|
+
belongsTo: [ CollisionGroup.ENTITY ],
|
72
|
+
collidesWith: [ CollisionGroup.BLOCK ],
|
73
|
+
}
|
74
|
+
},
|
75
|
+
],
|
67
76
|
linearVelocity: {
|
68
77
|
x: direction.x * 30,
|
69
78
|
y: direction.y * 30,
|
@@ -71,7 +80,7 @@ export default class RocketLauncherEntity extends GunEntity {
|
|
71
80
|
},
|
72
81
|
}
|
73
82
|
});
|
74
|
-
|
83
|
+
|
75
84
|
// Create a despawn timer if it doesn't hit
|
76
85
|
setTimeout(() => {
|
77
86
|
if (rocketMissileEntity.isSpawned) {
|
@@ -5,7 +5,7 @@ import type GamePlayerEntity from '../GamePlayerEntity';
|
|
5
5
|
|
6
6
|
const DEFAULT_SHOTGUN_OPTIONS: GunEntityOptions = {
|
7
7
|
ammo: 4,
|
8
|
-
damage:
|
8
|
+
damage: 13,
|
9
9
|
fireRate: 0.8,
|
10
10
|
heldHand: 'both',
|
11
11
|
iconImageUri: 'icons/shotgun.png',
|