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,56 @@
1
+ import { Block, Vector3Like, World } from 'hytopia';
2
+ import {
3
+ BEDROCK_BLOCK_ID,
4
+ BLOCK_ID_BREAK_DAMAGE,
5
+ BLOCK_ID_MATERIALS,
6
+ } from '../gameConfig';
7
+
8
+ interface BlockDamage {
9
+ blockId: number;
10
+ totalDamage: number;
11
+ }
12
+
13
+ export default class TerrainDamageManager {
14
+ public static instance: TerrainDamageManager = new TerrainDamageManager();
15
+
16
+ private _blockDamages: Map<string, BlockDamage> = new Map();
17
+
18
+ private constructor() {}
19
+
20
+ public static getBreakMaterialCount(blockId: number): number {
21
+ return BLOCK_ID_MATERIALS[blockId] ?? BLOCK_ID_MATERIALS.default;
22
+ }
23
+
24
+ public damageBlock(world: World, block: Block, damage: number): boolean {
25
+ const coordinateKey = this._coordinateToKey(block.globalCoordinate);
26
+ let blockDamage = this._blockDamages.get(coordinateKey);
27
+
28
+ if (!blockDamage) {
29
+ const blockId = block.blockType.id;
30
+
31
+ if (block.blockType.isLiquid || blockId === BEDROCK_BLOCK_ID) {
32
+ return false;
33
+ }
34
+
35
+ blockDamage = { blockId, totalDamage: 0 };
36
+ this._blockDamages.set(coordinateKey, blockDamage);
37
+ }
38
+
39
+ blockDamage.totalDamage += damage;
40
+
41
+ const requiredBreakDamage = BLOCK_ID_BREAK_DAMAGE[blockDamage.blockId] ?? BLOCK_ID_BREAK_DAMAGE.default;
42
+
43
+ if (blockDamage.totalDamage >= requiredBreakDamage) {
44
+ world.chunkLattice.setBlock(block.globalCoordinate, 0);
45
+ this._blockDamages.delete(coordinateKey);
46
+
47
+ return true;
48
+ }
49
+
50
+ return false;
51
+ }
52
+
53
+ private _coordinateToKey(coordinate: Vector3Like): string {
54
+ return `${coordinate.x},${coordinate.y},${coordinate.z}`;
55
+ }
56
+ }
@@ -0,0 +1,43 @@
1
+ import { Quaternion } from 'hytopia';
2
+ import ItemEntity from "../ItemEntity";
3
+ import GamePlayerEntity from '../GamePlayerEntity';
4
+ import type { ItemEntityOptions } from "../ItemEntity";
5
+
6
+ const ADD_HEALTH_AMOUNT = 50;
7
+
8
+ const DEFAULT_MEDPACK_OPTIONS: ItemEntityOptions = {
9
+ heldHand: 'right',
10
+ iconImageUri: 'icons/medpack.png',
11
+ idleAnimation: 'idle_gun_right',
12
+ mlAnimation: 'shoot_gun_right',
13
+ modelUri: 'models/items/medpack.glb',
14
+ modelScale: 0.4,
15
+ name: 'Med Pack',
16
+ consumable: true,
17
+ consumeAudioUri: 'audio/sfx/medpack-consume.mp3',
18
+ consumeTimeMs: 1000,
19
+ quantity: 1,
20
+ }
21
+
22
+ export default class MedPackEntity extends ItemEntity {
23
+ public constructor(options: Partial<ItemEntityOptions> = {}) {
24
+ super({ ...DEFAULT_MEDPACK_OPTIONS, ...options });
25
+ }
26
+
27
+ public override consume(): void {
28
+ if (!(this.parent instanceof GamePlayerEntity) || this.parent.health >= this.parent.maxHealth) {
29
+ return;
30
+ }
31
+
32
+ this.parent.updateHealth(ADD_HEALTH_AMOUNT);
33
+
34
+ super.consume();
35
+ }
36
+
37
+ public override equip(): void {
38
+ super.equip();
39
+
40
+ this.setPosition({ x: 0, y: 0.15, z: 0.3 });
41
+ this.setRotation(Quaternion.fromEuler(-90, 0, 270));
42
+ }
43
+ }
@@ -0,0 +1,43 @@
1
+ import { Quaternion } from 'hytopia';
2
+ import ItemEntity from "../ItemEntity";
3
+ import GamePlayerEntity from '../GamePlayerEntity';
4
+ import type { ItemEntityOptions } from "../ItemEntity";
5
+
6
+ const ADD_SHIELD_AMOUNT = 25;
7
+
8
+ const DEFAULT_SHIELD_POTION_OPTIONS: ItemEntityOptions = {
9
+ heldHand: 'right',
10
+ iconImageUri: 'icons/shield-potion.png',
11
+ idleAnimation: 'idle_gun_right',
12
+ mlAnimation: 'shoot_gun_right',
13
+ modelUri: 'models/items/shield-potion.glb',
14
+ modelScale: 0.4,
15
+ name: 'Shield Potion',
16
+ consumable: true,
17
+ consumeAudioUri: 'audio/sfx/shield-potion-consume.mp3',
18
+ consumeTimeMs: 1000,
19
+ quantity: 1,
20
+ }
21
+
22
+ export default class ShieldPotionEntity extends ItemEntity {
23
+ public constructor(options: Partial<ItemEntityOptions> = {}) {
24
+ super({ ...DEFAULT_SHIELD_POTION_OPTIONS, ...options });
25
+ }
26
+
27
+ public override consume(): void {
28
+ if (!(this.parent instanceof GamePlayerEntity) || this.parent.shield >= this.parent.maxShield) {
29
+ return;
30
+ }
31
+
32
+ this.parent.updateShield(ADD_SHIELD_AMOUNT);
33
+
34
+ super.consume();
35
+ }
36
+
37
+ public override equip(): void {
38
+ super.equip();
39
+
40
+ this.setPosition({ x: 0, y: 0.15, z: -0.2 });
41
+ this.setRotation(Quaternion.fromEuler(-90, 0, 0));
42
+ }
43
+ }
@@ -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_AK47_OPTIONS: GunEntityOptions = {
6
+ ammo: 25,
7
+ damage: 22,
8
+ fireRate: 5,
9
+ heldHand: 'both',
10
+ iconImageUri: 'icons/ak-47.png',
11
+ idleAnimation: 'idle_gun_both',
12
+ mlAnimation: 'shoot_gun_both',
13
+ name: 'AK-47',
14
+ maxAmmo: 25,
15
+ totalAmmo: 150,
16
+ scopeZoom: 2,
17
+ modelUri: 'models/items/ak-47.glb',
18
+ modelScale: 1.3,
19
+ range: 70,
20
+ reloadAudioUri: 'audio/sfx/rifle-reload.mp3',
21
+ reloadTimeMs: 2200,
22
+ shootAudioUri: 'audio/sfx/rifle-shoot.mp3',
23
+ };
24
+
25
+ export default class AK47Entity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_AK47_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: -1.25 },
39
+ rotation: Quaternion.fromEuler(0, 90, 0),
40
+ };
41
+ }
42
+ }
43
+
@@ -0,0 +1,80 @@
1
+ import { Quaternion, Vector3Like, QuaternionLike } from 'hytopia';
2
+ import GunEntity from '../GunEntity';
3
+ import type { GunEntityOptions } from '../GunEntity';
4
+
5
+ const DEFAULT_AUTO_SHOTGUN_OPTIONS: GunEntityOptions = {
6
+ ammo: 6,
7
+ damage: 10, // Per pellet (7 pellets = 70 max damage)
8
+ fireRate: 1.5,
9
+ heldHand: 'both',
10
+ iconImageUri: 'icons/auto-shotgun.png',
11
+ idleAnimation: 'idle_gun_both',
12
+ mlAnimation: 'shoot_gun_both',
13
+ name: 'Auto Shotgun',
14
+ maxAmmo: 6,
15
+ totalAmmo: 30,
16
+ modelUri: 'models/items/auto-shotgun.glb',
17
+ modelScale: 1.2,
18
+ range: 8,
19
+ reloadAudioUri: 'audio/sfx/shotgun-reload.mp3',
20
+ reloadTimeMs: 3500,
21
+ shootAudioUri: 'audio/sfx/shotgun-shoot.mp3',
22
+ };
23
+
24
+ export default class AutoShotgunEntity extends GunEntity {
25
+ public constructor(options: Partial<GunEntityOptions> = {}) {
26
+ super({ ...DEFAULT_AUTO_SHOTGUN_OPTIONS, ...options });
27
+ }
28
+
29
+ public override shoot(): void {
30
+ if (!this.parent || !this.processShoot()) return;
31
+
32
+ super.shoot();
33
+ }
34
+
35
+ public override getMuzzleFlashPositionRotation(): { position: Vector3Like, rotation: QuaternionLike } {
36
+ return {
37
+ position: { x: 0.015, y: 0, z: -1 },
38
+ rotation: Quaternion.fromEuler(0, 90, 0),
39
+ };
40
+ }
41
+
42
+ public override shootRaycast(origin: Vector3Like, direction: Vector3Like, length: number) {
43
+ // Create spread pattern for auto-shotgun pellets using angles relative to direction
44
+ const spreadAngles = [
45
+ { x: 0, y: 0 }, // Center
46
+ { x: 0.05, y: 0.05 }, // Upper right
47
+ { x: -0.05, y: 0.05 }, // Upper left
48
+ { x: 0.07, y: 0 }, // Right
49
+ { x: -0.07, y: 0 }, // Left
50
+ { x: 0.05, y: -0.05 }, // Lower right
51
+ { x: -0.05, y: -0.05 } // Lower left
52
+ ];
53
+
54
+ // Fire each pellet with spread applied to original direction
55
+ for (const angle of spreadAngles) {
56
+ // Calculate spread direction relative to original direction
57
+ const spreadDirection = {
58
+ x: direction.x + (direction.z * angle.x), // Add horizontal spread
59
+ y: direction.y + angle.y, // Add vertical spread
60
+ z: direction.z - (direction.x * angle.x) // Maintain direction magnitude
61
+ };
62
+
63
+ // Normalize the spread direction to maintain consistent range
64
+ const magnitude = Math.sqrt(
65
+ spreadDirection.x * spreadDirection.x +
66
+ spreadDirection.y * spreadDirection.y +
67
+ spreadDirection.z * spreadDirection.z
68
+ );
69
+
70
+ const normalizedDirection = {
71
+ x: spreadDirection.x / magnitude,
72
+ y: spreadDirection.y / magnitude,
73
+ z: spreadDirection.z / magnitude
74
+ };
75
+
76
+ super.shootRaycast(origin, normalizedDirection, length);
77
+ }
78
+ }
79
+ }
80
+
@@ -0,0 +1,46 @@
1
+ import { Quaternion, Vector3Like, QuaternionLike } from 'hytopia';
2
+ import GunEntity from '../GunEntity';
3
+ import type { GunEntityOptions } from '../GunEntity';
4
+
5
+ const DEFAULT_BOLT_ACTION_SNIPER_OPTIONS: GunEntityOptions = {
6
+ ammo: 1,
7
+ damage: 75,
8
+ fireRate: 0.5,
9
+ heldHand: 'both',
10
+ iconImageUri: 'icons/bolt-action-sniper.png',
11
+ idleAnimation: 'idle_gun_both',
12
+ mlAnimation: 'shoot_gun_both',
13
+ name: 'Bolt Action Sniper',
14
+ maxAmmo: 1,
15
+ totalAmmo: 12,
16
+ scopeZoom: 5,
17
+ modelUri: 'models/items/bolt-action-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 BoltActionSniperEntity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_BOLT_ACTION_SNIPER_OPTIONS, ...options });
28
+ }
29
+
30
+ public override shoot(): void {
31
+ if (!this.parent || !this.processShoot()) return;
32
+
33
+ super.shoot();
34
+
35
+ // It's bolt action, auto reload it 300ms after a shot.
36
+ setTimeout(() => { this.reload() }, 300);
37
+ }
38
+
39
+ public override getMuzzleFlashPositionRotation(): { position: Vector3Like, rotation: QuaternionLike } {
40
+ return { // TODO: FIX MUZZLE FLASH POSITION
41
+ position: { x: 0, y: 0.01, z: -1.25 },
42
+ rotation: Quaternion.fromEuler(0, 90, 0),
43
+ };
44
+ }
45
+ }
46
+
@@ -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_LIGHT_MACHINE_GUN_OPTIONS: GunEntityOptions = {
6
+ ammo: 50,
7
+ damage: 15,
8
+ fireRate: 10,
9
+ heldHand: 'both',
10
+ iconImageUri: 'icons/light-machine-gun.png',
11
+ idleAnimation: 'idle_gun_both',
12
+ mlAnimation: 'shoot_gun_both',
13
+ name: 'Light Machine Gun',
14
+ maxAmmo: 50,
15
+ totalAmmo: 300,
16
+ scopeZoom: 1.5,
17
+ modelUri: 'models/items/light-machine-gun.glb',
18
+ modelScale: 1.3,
19
+ range: 50,
20
+ reloadAudioUri: 'audio/sfx/machine-gun-reload.mp3',
21
+ reloadTimeMs: 4200,
22
+ shootAudioUri: 'audio/sfx/machine-gun-shoot.mp3',
23
+ };
24
+
25
+ export default class LightMachineGunEntity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_LIGHT_MACHINE_GUN_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.05, z: -1.7 },
39
+ rotation: Quaternion.fromEuler(0, 90, 0),
40
+ };
41
+ }
42
+ }
43
+
@@ -0,0 +1,38 @@
1
+ import { Quaternion } from 'hytopia';
2
+ import MeleeWeaponEntity from '../MeleeWeaponEntity';
3
+ import type { MeleeWeaponEntityOptions } from '../MeleeWeaponEntity';
4
+
5
+ const DEFAULT_MINING_DRILL_OPTIONS: MeleeWeaponEntityOptions = {
6
+ damage: 40,
7
+ attackRate: 5,
8
+ heldHand: 'right',
9
+ iconImageUri: 'icons/mining-drill.png',
10
+ idleAnimation: 'idle_gun_right',
11
+ mlAnimation: 'shoot_gun_right',
12
+ name: 'Mining Drill',
13
+ modelUri: 'models/items/mining-drill.glb',
14
+ modelScale: 0.05,
15
+ range: 1.5,
16
+ minesMaterials: true,
17
+ attackAudioUri: 'audio/sfx/mining-drill-drilling.mp3',
18
+ hitAudioUri: 'audio/sfx/dig/dig-stone.mp3',
19
+ };
20
+
21
+ export default class MiningDrillEntity extends MeleeWeaponEntity {
22
+ public constructor(options: Partial<MeleeWeaponEntityOptions> = {}) {
23
+ super({ ...DEFAULT_MINING_DRILL_OPTIONS, ...options, tag: 'mining-drill' });
24
+ }
25
+
26
+ public override attack(): void {
27
+ if (!this.parent || !this.processAttack()) return;
28
+
29
+ super.attack();
30
+ }
31
+
32
+ public override equip(): void {
33
+ super.equip();
34
+
35
+ this.setPosition({ x: -0.3, y: 0.5, z: -0.1 });
36
+ this.setRotation(Quaternion.fromEuler(180, 0, -90));
37
+ }
38
+ }
@@ -0,0 +1,38 @@
1
+ import { Quaternion } from 'hytopia';
2
+ import MeleeWeaponEntity from '../MeleeWeaponEntity';
3
+ import type { MeleeWeaponEntityOptions } from '../MeleeWeaponEntity';
4
+
5
+ const DEFAULT_PICKAXE_OPTIONS: MeleeWeaponEntityOptions = {
6
+ damage: 10, // 10 hits to kill unshielded
7
+ attackRate: 4.5, // Slower attack rate to prevent spam
8
+ heldHand: 'right',
9
+ iconImageUri: 'icons/pickaxe.png',
10
+ idleAnimation: 'idle_gun_right',
11
+ mlAnimation: 'simple_interact',
12
+ name: 'Pickaxe',
13
+ modelUri: 'models/items/pickaxe.gltf',
14
+ modelScale: 1.25,
15
+ range: 2,
16
+ minesMaterials: true,
17
+ attackAudioUri: 'audio/sfx/player/player-swing-woosh.mp3',
18
+ hitAudioUri: 'audio/sfx/dig/dig-stone.mp3',
19
+ };
20
+
21
+ export default class PickaxeEntity extends MeleeWeaponEntity {
22
+ public constructor(options: Partial<MeleeWeaponEntityOptions> = {}) {
23
+ super({ ...DEFAULT_PICKAXE_OPTIONS, ...options, tag: 'pickaxe' });
24
+ }
25
+
26
+ public override attack(): void {
27
+ if (!this.parent || !this.processAttack()) return;
28
+
29
+ super.attack();
30
+ }
31
+
32
+ public override equip(): void {
33
+ super.equip();
34
+
35
+ this.setPosition({ x: 0, y: 0.2, z: 0 });
36
+ this.setRotation(Quaternion.fromEuler(-90, 0, 90));
37
+ }
38
+ }
@@ -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_PISTOL_OPTIONS: GunEntityOptions = {
7
+ ammo: 15,
8
+ damage: 18,
9
+ fireRate: 6,
10
+ heldHand: 'right',
11
+ iconImageUri: 'icons/pistol.png',
12
+ idleAnimation: 'idle_gun_right',
13
+ mlAnimation: 'shoot_gun_right',
14
+ name: 'Pistol',
15
+ maxAmmo: 15,
16
+ totalAmmo: 75,
17
+ modelUri: 'models/items/pistol.glb',
18
+ modelScale: 1.3,
19
+ range: 30,
20
+ reloadAudioUri: 'audio/sfx/pistol-reload.mp3',
21
+ reloadTimeMs: 1500,
22
+ shootAudioUri: 'audio/sfx/pistol-shoot.mp3',
23
+ };
24
+
25
+ export default class PistolEntity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_PISTOL_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.1, z: -0.5 },
42
+ rotation: Quaternion.fromEuler(0, 90, 0),
43
+ };
44
+ }
45
+ }
46
+
@@ -0,0 +1,186 @@
1
+ import { Audio, Entity, Quaternion, Vector3Like, QuaternionLike, RigidBodyType, EntityEvent, Vector3 } from 'hytopia';
2
+ import GunEntity from '../GunEntity';
3
+ import { BEDROCK_BLOCK_ID } from '../../gameConfig';
4
+ import type { GunEntityOptions } from '../GunEntity';
5
+ import type GamePlayerEntity from '../GamePlayerEntity';
6
+
7
+ const ROCKET_DESTRUCTION_RADIUS = 4;
8
+
9
+ const DEFAULT_ROCKET_LAUNCHER_OPTIONS: GunEntityOptions = {
10
+ ammo: 1,
11
+ damage: 80,
12
+ fireRate: 0.8,
13
+ heldHand: 'right',
14
+ iconImageUri: 'icons/rocket-launcher.png',
15
+ idleAnimation: 'idle_gun_right',
16
+ mlAnimation: 'shoot_gun_right',
17
+ name: 'Rocket Launcher',
18
+ maxAmmo: 1,
19
+ totalAmmo: 5,
20
+ modelUri: 'models/items/rocket-launcher.glb',
21
+ modelScale: 1.3,
22
+ range: 8,
23
+ reloadAudioUri: 'audio/sfx/rocket-launcher-reload.mp3',
24
+ reloadTimeMs: 2500,
25
+ shootAudioUri: 'audio/sfx/rocket-launcher-shoot.mp3',
26
+ };
27
+
28
+ export default class RocketLauncherEntity extends GunEntity {
29
+ public constructor(options: Partial<GunEntityOptions> = {}) {
30
+ super({ ...DEFAULT_ROCKET_LAUNCHER_OPTIONS, ...options });
31
+ }
32
+
33
+ public override shoot(): void {
34
+ if (!this.parent || !this.processShoot()) return;
35
+
36
+ super.shoot();
37
+
38
+ // Cancel input since rocket launcher requires click-to-shoot
39
+ (this.parent as GamePlayerEntity).player.input.ml = false;
40
+ }
41
+
42
+ public override getMuzzleFlashPositionRotation(): { position: Vector3Like, rotation: QuaternionLike } {
43
+ return {
44
+ position: { x: 0.03, y: 0.6, z: -1.5 },
45
+ rotation: Quaternion.fromEuler(0, 90, 0),
46
+ };
47
+ }
48
+
49
+ public override equip(): void {
50
+ super.equip();
51
+
52
+ this.setPosition({ x: 0, y: 0.3, z: 0.4 });
53
+ }
54
+
55
+ public override shootRaycast(origin: Vector3Like, direction: Vector3Like, length: number) {
56
+ // Instead of a raycast, we'll spawn a projectile that on collision with a block or entity explodes
57
+ // and deals damage and blows up blocks
58
+ if (!this.parent?.world) {
59
+ return;
60
+ }
61
+
62
+ const rocketMissileEntity = new Entity({
63
+ modelUri: 'models/items/rocket-missile.glb',
64
+ modelScale: 0.75,
65
+ rigidBodyOptions: {
66
+ type: RigidBodyType.KINEMATIC_VELOCITY,
67
+ linearVelocity: {
68
+ x: direction.x * 30,
69
+ y: direction.y * 30,
70
+ z: direction.z * 30,
71
+ },
72
+ }
73
+ });
74
+
75
+ // Create a despawn timer if it doesn't hit
76
+ setTimeout(() => {
77
+ if (rocketMissileEntity.isSpawned) {
78
+ rocketMissileEntity.despawn();
79
+ }
80
+ }, 3000);
81
+
82
+ // Convert direction vector to quaternion that faces in the direction vector
83
+ const directionQuat = Quaternion.fromEuler(
84
+ Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI,
85
+ Math.atan2(direction.x, direction.z) * 180 / Math.PI,
86
+ 0
87
+ );
88
+
89
+ rocketMissileEntity.on(EntityEvent.BLOCK_COLLISION, ({ blockType, colliderHandleA, colliderHandleB }) => {
90
+ if (!this.parent?.world || !rocketMissileEntity.isSpawned || blockType.isLiquid) {
91
+ return;
92
+ }
93
+
94
+ const { world } = this.parent;
95
+ const contactManifold = world.simulation.getContactManifolds(colliderHandleA, colliderHandleB)[0];
96
+
97
+ if (!contactManifold) {
98
+ return;
99
+ }
100
+
101
+ const contactPoint = contactManifold.contactPoints[0];
102
+ const contactCoordinate = {
103
+ x: Math.floor(contactPoint.x),
104
+ y: Math.floor(contactPoint.y),
105
+ z: Math.floor(contactPoint.z)
106
+ };
107
+
108
+ // Deal damage to nearby players
109
+ this.parent.world.entityManager.getAllPlayerEntities().forEach(playerEntity => {
110
+ const playerPos = Vector3.fromVector3Like(playerEntity.position);
111
+ const contactPos = Vector3.fromVector3Like(contactPoint);
112
+ const distance = playerPos.distance(contactPos);
113
+
114
+ if (distance <= ROCKET_DESTRUCTION_RADIUS) {
115
+ (playerEntity as GamePlayerEntity).takeDamage(this.damage, direction, this.parent as GamePlayerEntity);
116
+ }
117
+ });
118
+
119
+ // Break blocks
120
+ for (let dx = -ROCKET_DESTRUCTION_RADIUS; dx <= ROCKET_DESTRUCTION_RADIUS; dx++) {
121
+ for (let dy = -ROCKET_DESTRUCTION_RADIUS; dy <= ROCKET_DESTRUCTION_RADIUS; dy++) {
122
+ for (let dz = -ROCKET_DESTRUCTION_RADIUS; dz <= ROCKET_DESTRUCTION_RADIUS; dz++) {
123
+ // Calculate distance from center of explosion
124
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
125
+
126
+ // Only destroy blocks within the spherical radius
127
+ if (distance > ROCKET_DESTRUCTION_RADIUS) {
128
+ continue;
129
+ }
130
+
131
+ const coordinate = {
132
+ x: contactCoordinate.x + dx,
133
+ y: contactCoordinate.y + dy,
134
+ z: contactCoordinate.z + dz
135
+ }
136
+
137
+ // do not destroy bedrock!
138
+ if (world.chunkLattice.getBlockId(coordinate) !== BEDROCK_BLOCK_ID) {
139
+ world.chunkLattice.setBlock(coordinate, 0);
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ // Explosion Visual
146
+ const explosionEntity = new Entity({
147
+ modelUri: 'models/environment/explosion.glb',
148
+ modelScale: 0.2,
149
+ rigidBodyOptions: { type: RigidBodyType.KINEMATIC_POSITION },
150
+ });
151
+
152
+ const explosionDirectionQuat = Quaternion.fromEuler(
153
+ Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI + 90,
154
+ Math.atan2(direction.x, direction.z) * 180 / Math.PI + 180, // Add 180 degrees to invert direction
155
+ 0
156
+ );
157
+
158
+ explosionEntity.spawn(world, contactPoint, explosionDirectionQuat);
159
+ explosionEntity.setCollisionGroupsForSolidColliders({
160
+ belongsTo: [],
161
+ collidesWith: [],
162
+ })
163
+
164
+ const explosionEffectInterval = setInterval(() => {
165
+ if (explosionEntity.opacity <= 0) {
166
+ explosionEntity.despawn();
167
+ clearInterval(explosionEffectInterval);
168
+ return;
169
+ }
170
+
171
+ explosionEntity.setOpacity(explosionEntity.opacity - 0.1);
172
+ }, 100);
173
+
174
+ // Explosion Audio
175
+ (new Audio({
176
+ uri: 'audio/sfx/rocket-launcher-explosion.mp3',
177
+ referenceDistance: 20,
178
+ volume: 0.4,
179
+ })).play(world);
180
+
181
+ rocketMissileEntity.despawn();
182
+ });
183
+
184
+ rocketMissileEntity.spawn(this.parent.world, origin, directionQuat);
185
+ }
186
+ }