hytopia 0.3.6 → 0.3.8

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 (147) 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/potion-consume.mp3 +0 -0
  14. package/examples/hygrounds/assets/audio/sfx/rifle-reload.mp3 +0 -0
  15. package/examples/hygrounds/assets/audio/sfx/rifle-shoot.mp3 +0 -0
  16. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-explosion.mp3 +0 -0
  17. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-reload.mp3 +0 -0
  18. package/examples/hygrounds/assets/audio/sfx/rocket-launcher-shoot.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/auto-sniper.png +0 -0
  28. package/examples/hygrounds/assets/icons/block.png +0 -0
  29. package/examples/hygrounds/assets/icons/bolt-action-sniper.png +0 -0
  30. package/examples/hygrounds/assets/icons/crown-bronze.png +0 -0
  31. package/examples/hygrounds/assets/icons/crown-gold.png +0 -0
  32. package/examples/hygrounds/assets/icons/crown-silver.png +0 -0
  33. package/examples/hygrounds/assets/icons/gravity-potion.png +0 -0
  34. package/examples/hygrounds/assets/icons/heart.png +0 -0
  35. package/examples/hygrounds/assets/icons/light-machine-gun.png +0 -0
  36. package/examples/hygrounds/assets/icons/medpack.png +0 -0
  37. package/examples/hygrounds/assets/icons/mining-drill.png +0 -0
  38. package/examples/hygrounds/assets/icons/pickaxe.png +0 -0
  39. package/examples/hygrounds/assets/icons/pistol.png +0 -0
  40. package/examples/hygrounds/assets/icons/revolver.png +0 -0
  41. package/examples/hygrounds/assets/icons/rocket-launcher.png +0 -0
  42. package/examples/hygrounds/assets/icons/shield-potion.png +0 -0
  43. package/examples/hygrounds/assets/icons/shield.png +0 -0
  44. package/examples/hygrounds/assets/icons/shotgun.png +0 -0
  45. package/examples/hygrounds/assets/icons/submachine-gun.png +0 -0
  46. package/examples/hygrounds/assets/map.json +31796 -0
  47. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion-named-nodes.glb +0 -0
  48. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb +0 -0
  49. package/examples/hygrounds/assets/models/environment/.optimized/explosion/explosion.glb.md5 +1 -0
  50. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2-named-nodes.glb +0 -0
  51. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb +0 -0
  52. package/examples/hygrounds/assets/models/environment/.optimized/explosion-2/explosion-2.glb.md5 +1 -0
  53. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3-named-nodes.glb +0 -0
  54. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb +0 -0
  55. package/examples/hygrounds/assets/models/environment/.optimized/explosion-3/explosion-3.glb.md5 +1 -0
  56. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter-named-nodes.glb +0 -0
  57. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb +0 -0
  58. package/examples/hygrounds/assets/models/environment/.optimized/helicopter/helicopter.glb.md5 +1 -0
  59. package/examples/hygrounds/assets/models/environment/chest.gltf +1 -0
  60. package/examples/hygrounds/assets/models/environment/explosion.glb +0 -0
  61. package/examples/hygrounds/assets/models/environment/muzzle-flash.gltf +1 -0
  62. package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper-named-nodes.glb +0 -0
  63. package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper.glb +0 -0
  64. package/examples/hygrounds/assets/models/items/.optimized/auto-sniper/auto-sniper.glb.md5 +1 -0
  65. package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion-named-nodes.glb +0 -0
  66. package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion.glb +0 -0
  67. package/examples/hygrounds/assets/models/items/.optimized/gravity-potion/gravity-potion.glb.md5 +1 -0
  68. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit-named-nodes.glb +0 -0
  69. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb +0 -0
  70. package/examples/hygrounds/assets/models/items/.optimized/medkit/medkit.glb.md5 +1 -0
  71. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack-named-nodes.glb +0 -0
  72. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb +0 -0
  73. package/examples/hygrounds/assets/models/items/.optimized/medpack/medpack.glb.md5 +1 -0
  74. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill-named-nodes.glb +0 -0
  75. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb +0 -0
  76. package/examples/hygrounds/assets/models/items/.optimized/mining-drill/mining-drill.glb.md5 +1 -0
  77. package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver-named-nodes.glb +0 -0
  78. package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver.glb +0 -0
  79. package/examples/hygrounds/assets/models/items/.optimized/revolver/revolver.glb.md5 +1 -0
  80. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile-named-nodes.glb +0 -0
  81. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb +0 -0
  82. package/examples/hygrounds/assets/models/items/.optimized/rocket-missile/rocket-missile.glb.md5 +1 -0
  83. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion-named-nodes.glb +0 -0
  84. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb +0 -0
  85. package/examples/hygrounds/assets/models/items/.optimized/shield-potion/shield-potion.glb.md5 +1 -0
  86. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2-named-nodes.glb +0 -0
  87. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb +0 -0
  88. package/examples/hygrounds/assets/models/items/.optimized/shield-potion-2/shield-potion-2.glb.md5 +1 -0
  89. package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun-named-nodes.glb +0 -0
  90. package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun.glb +0 -0
  91. package/examples/hygrounds/assets/models/items/.optimized/submachine-gun/submachine-gun.glb.md5 +1 -0
  92. package/examples/hygrounds/assets/models/items/ak-47.glb +0 -0
  93. package/examples/hygrounds/assets/models/items/auto-shotgun.glb +0 -0
  94. package/examples/hygrounds/assets/models/items/auto-sniper.glb +0 -0
  95. package/examples/hygrounds/assets/models/items/bolt-action-sniper.glb +0 -0
  96. package/examples/hygrounds/assets/models/items/gravity-potion.glb +0 -0
  97. package/examples/hygrounds/assets/models/items/light-machine-gun.glb +0 -0
  98. package/examples/hygrounds/assets/models/items/medpack.glb +0 -0
  99. package/examples/hygrounds/assets/models/items/mining-drill.glb +0 -0
  100. package/examples/hygrounds/assets/models/items/pickaxe.gltf +1 -0
  101. package/examples/hygrounds/assets/models/items/pistol.glb +0 -0
  102. package/examples/hygrounds/assets/models/items/revolver.glb +0 -0
  103. package/examples/hygrounds/assets/models/items/rocket-launcher.glb +0 -0
  104. package/examples/hygrounds/assets/models/items/rocket-missile.glb +0 -0
  105. package/examples/hygrounds/assets/models/items/shield-potion.glb +0 -0
  106. package/examples/hygrounds/assets/models/items/shotgun.glb +0 -0
  107. package/examples/hygrounds/assets/models/items/submachine-gun.glb +0 -0
  108. package/examples/hygrounds/assets/models/players/soldier-player.gltf +1 -0
  109. package/examples/hygrounds/assets/ui/images/scope.png +0 -0
  110. package/examples/hygrounds/assets/ui/index.html +1122 -0
  111. package/examples/hygrounds/bun.lock +503 -0
  112. package/examples/hygrounds/classes/ChestEntity.ts +133 -0
  113. package/examples/hygrounds/classes/GameManager.ts +422 -0
  114. package/examples/hygrounds/classes/GamePlayerEntity.ts +595 -0
  115. package/examples/hygrounds/classes/GunEntity.ts +259 -0
  116. package/examples/hygrounds/classes/ItemEntity.ts +225 -0
  117. package/examples/hygrounds/classes/ItemFactory.ts +61 -0
  118. package/examples/hygrounds/classes/MeleeWeaponEntity.ts +138 -0
  119. package/examples/hygrounds/classes/TerrainDamageManager.ts +56 -0
  120. package/examples/hygrounds/classes/items/GravityPotionEntity.ts +48 -0
  121. package/examples/hygrounds/classes/items/MedPackEntity.ts +43 -0
  122. package/examples/hygrounds/classes/items/ShieldPotionEntity.ts +43 -0
  123. package/examples/hygrounds/classes/weapons/AK47Entity.ts +43 -0
  124. package/examples/hygrounds/classes/weapons/AutoShotgunEntity.ts +80 -0
  125. package/examples/hygrounds/classes/weapons/AutoSniperEntity.ts +43 -0
  126. package/examples/hygrounds/classes/weapons/BoltActionSniperEntity.ts +46 -0
  127. package/examples/hygrounds/classes/weapons/LightMachineGunEntity.ts +43 -0
  128. package/examples/hygrounds/classes/weapons/MiningDrillEntity.ts +38 -0
  129. package/examples/hygrounds/classes/weapons/PickaxeEntity.ts +38 -0
  130. package/examples/hygrounds/classes/weapons/PistolEntity.ts +46 -0
  131. package/examples/hygrounds/classes/weapons/RevolverEntity.ts +46 -0
  132. package/examples/hygrounds/classes/weapons/RocketLauncherEntity.ts +195 -0
  133. package/examples/hygrounds/classes/weapons/ShotgunEntity.ts +84 -0
  134. package/examples/hygrounds/classes/weapons/SubmachineGunEntity.ts +43 -0
  135. package/examples/hygrounds/gameConfig.ts +430 -0
  136. package/examples/hygrounds/index.ts +41 -0
  137. package/examples/hygrounds/package.json +16 -0
  138. package/examples/player-persistence/README.md +3 -0
  139. package/examples/player-persistence/assets/map.json +2623 -0
  140. package/examples/player-persistence/dev/persistence/player-player-1.json +6 -0
  141. package/examples/player-persistence/dev/persistence/test.json +3 -0
  142. package/examples/player-persistence/index.ts +126 -0
  143. package/examples/player-persistence/package.json +16 -0
  144. package/package.json +1 -1
  145. package/server.api.json +21 -0
  146. package/server.d.ts +2 -1
  147. package/server.js +113 -113
@@ -0,0 +1,195 @@
1
+ import { Audio, CollisionGroup, Entity, Quaternion, Vector3Like, QuaternionLike, RigidBodyType, EntityEvent, Vector3, Collider } 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
+ 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
+ ],
76
+ linearVelocity: {
77
+ x: direction.x * 30,
78
+ y: direction.y * 30,
79
+ z: direction.z * 30,
80
+ },
81
+ }
82
+ });
83
+
84
+ // Create a despawn timer if it doesn't hit
85
+ setTimeout(() => {
86
+ if (rocketMissileEntity.isSpawned) {
87
+ rocketMissileEntity.despawn();
88
+ }
89
+ }, 3000);
90
+
91
+ // Convert direction vector to quaternion that faces in the direction vector
92
+ const directionQuat = Quaternion.fromEuler(
93
+ Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI,
94
+ Math.atan2(direction.x, direction.z) * 180 / Math.PI,
95
+ 0
96
+ );
97
+
98
+ rocketMissileEntity.on(EntityEvent.BLOCK_COLLISION, ({ blockType, colliderHandleA, colliderHandleB }) => {
99
+ if (!this.parent?.world || !rocketMissileEntity.isSpawned || blockType.isLiquid) {
100
+ return;
101
+ }
102
+
103
+ const { world } = this.parent;
104
+ const contactManifold = world.simulation.getContactManifolds(colliderHandleA, colliderHandleB)[0];
105
+
106
+ if (!contactManifold) {
107
+ return;
108
+ }
109
+
110
+ const contactPoint = contactManifold.contactPoints[0];
111
+ const contactCoordinate = {
112
+ x: Math.floor(contactPoint.x),
113
+ y: Math.floor(contactPoint.y),
114
+ z: Math.floor(contactPoint.z)
115
+ };
116
+
117
+ // Deal damage to nearby players
118
+ this.parent.world.entityManager.getAllPlayerEntities().forEach(playerEntity => {
119
+ const playerPos = Vector3.fromVector3Like(playerEntity.position);
120
+ const contactPos = Vector3.fromVector3Like(contactPoint);
121
+ const distance = playerPos.distance(contactPos);
122
+
123
+ if (distance <= ROCKET_DESTRUCTION_RADIUS) {
124
+ (playerEntity as GamePlayerEntity).takeDamage(this.damage, direction, this.parent as GamePlayerEntity);
125
+ }
126
+ });
127
+
128
+ // Break blocks
129
+ for (let dx = -ROCKET_DESTRUCTION_RADIUS; dx <= ROCKET_DESTRUCTION_RADIUS; dx++) {
130
+ for (let dy = -ROCKET_DESTRUCTION_RADIUS; dy <= ROCKET_DESTRUCTION_RADIUS; dy++) {
131
+ for (let dz = -ROCKET_DESTRUCTION_RADIUS; dz <= ROCKET_DESTRUCTION_RADIUS; dz++) {
132
+ // Calculate distance from center of explosion
133
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
134
+
135
+ // Only destroy blocks within the spherical radius
136
+ if (distance > ROCKET_DESTRUCTION_RADIUS) {
137
+ continue;
138
+ }
139
+
140
+ const coordinate = {
141
+ x: contactCoordinate.x + dx,
142
+ y: contactCoordinate.y + dy,
143
+ z: contactCoordinate.z + dz
144
+ }
145
+
146
+ // do not destroy bedrock!
147
+ if (world.chunkLattice.getBlockId(coordinate) !== BEDROCK_BLOCK_ID) {
148
+ world.chunkLattice.setBlock(coordinate, 0);
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ // Explosion Visual
155
+ const explosionEntity = new Entity({
156
+ modelUri: 'models/environment/explosion.glb',
157
+ modelScale: 0.2,
158
+ rigidBodyOptions: { type: RigidBodyType.KINEMATIC_POSITION },
159
+ });
160
+
161
+ const explosionDirectionQuat = Quaternion.fromEuler(
162
+ Math.atan2(-direction.y, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI + 90,
163
+ Math.atan2(direction.x, direction.z) * 180 / Math.PI + 180, // Add 180 degrees to invert direction
164
+ 0
165
+ );
166
+
167
+ explosionEntity.spawn(world, contactPoint, explosionDirectionQuat);
168
+ explosionEntity.setCollisionGroupsForSolidColliders({
169
+ belongsTo: [],
170
+ collidesWith: [],
171
+ })
172
+
173
+ const explosionEffectInterval = setInterval(() => {
174
+ if (explosionEntity.opacity <= 0) {
175
+ explosionEntity.despawn();
176
+ clearInterval(explosionEffectInterval);
177
+ return;
178
+ }
179
+
180
+ explosionEntity.setOpacity(explosionEntity.opacity - 0.1);
181
+ }, 100);
182
+
183
+ // Explosion Audio
184
+ (new Audio({
185
+ uri: 'audio/sfx/rocket-launcher-explosion.mp3',
186
+ referenceDistance: 20,
187
+ volume: 0.4,
188
+ })).play(world);
189
+
190
+ rocketMissileEntity.despawn();
191
+ });
192
+
193
+ rocketMissileEntity.spawn(this.parent.world, origin, directionQuat);
194
+ }
195
+ }
@@ -0,0 +1,84 @@
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_SHOTGUN_OPTIONS: GunEntityOptions = {
7
+ ammo: 4,
8
+ damage: 13,
9
+ fireRate: 0.8,
10
+ heldHand: 'both',
11
+ iconImageUri: 'icons/shotgun.png',
12
+ idleAnimation: 'idle_gun_both',
13
+ mlAnimation: 'shoot_gun_both',
14
+ name: 'Shotgun',
15
+ maxAmmo: 4,
16
+ totalAmmo: 24,
17
+ modelUri: 'models/items/shotgun.glb',
18
+ modelScale: 1.2,
19
+ range: 8,
20
+ reloadAudioUri: 'audio/sfx/shotgun-reload.mp3',
21
+ reloadTimeMs: 3000,
22
+ shootAudioUri: 'audio/sfx/shotgun-shoot.mp3',
23
+ };
24
+
25
+ export default class ShotgunEntity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_SHOTGUN_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 shotgun 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: -1.5 },
42
+ rotation: Quaternion.fromEuler(0, 90, 0),
43
+ };
44
+ }
45
+
46
+ public override shootRaycast(origin: Vector3Like, direction: Vector3Like, length: number) {
47
+ // Create spread pattern for shotgun pellets using angles relative to direction
48
+ const spreadAngles = [
49
+ { x: 0, y: 0 }, // Center
50
+ { x: 0.05, y: 0.05 }, // Upper right
51
+ { x: -0.05, y: 0.05 }, // Upper left
52
+ { x: 0.07, y: 0 }, // Right
53
+ { x: -0.07, y: 0 }, // Left
54
+ { x: 0.05, y: -0.05 }, // Lower right
55
+ { x: -0.05, y: -0.05 } // Lower left
56
+ ];
57
+
58
+ // Fire each pellet with spread applied to original direction
59
+ for (const angle of spreadAngles) {
60
+ // Calculate spread direction relative to original direction
61
+ const spreadDirection = {
62
+ x: direction.x + (direction.z * angle.x), // Add horizontal spread
63
+ y: direction.y + angle.y, // Add vertical spread
64
+ z: direction.z - (direction.x * angle.x) // Maintain direction magnitude
65
+ };
66
+
67
+ // Normalize the spread direction to maintain consistent range
68
+ const magnitude = Math.sqrt(
69
+ spreadDirection.x * spreadDirection.x +
70
+ spreadDirection.y * spreadDirection.y +
71
+ spreadDirection.z * spreadDirection.z
72
+ );
73
+
74
+ const normalizedDirection = {
75
+ x: spreadDirection.x / magnitude,
76
+ y: spreadDirection.y / magnitude,
77
+ z: spreadDirection.z / magnitude
78
+ };
79
+
80
+ super.shootRaycast(origin, normalizedDirection, length);
81
+ }
82
+ }
83
+ }
84
+
@@ -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_SUBMACHINE_GUN_OPTIONS: GunEntityOptions = {
6
+ ammo: 60,
7
+ damage: 6,
8
+ fireRate: 12,
9
+ heldHand: 'right',
10
+ iconImageUri: 'icons/submachine-gun.png',
11
+ idleAnimation: 'idle_gun_right',
12
+ mlAnimation: 'shoot_gun_right',
13
+ name: 'Submachine Gun',
14
+ maxAmmo: 60,
15
+ totalAmmo: 240,
16
+ scopeZoom: 1.35,
17
+ modelUri: 'models/items/submachine-gun.glb',
18
+ modelScale: 1.3,
19
+ range: 35,
20
+ reloadAudioUri: 'audio/sfx/rifle-reload.mp3',
21
+ reloadTimeMs: 1500,
22
+ shootAudioUri: 'audio/sfx/pistol-shoot.mp3',
23
+ };
24
+
25
+ export default class SubmachineGunEntity extends GunEntity {
26
+ public constructor(options: Partial<GunEntityOptions> = {}) {
27
+ super({ ...DEFAULT_SUBMACHINE_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: -0.95 },
39
+ rotation: Quaternion.fromEuler(0, 90, 0),
40
+ };
41
+ }
42
+ }
43
+