hytopia 0.1.95 → 0.1.97
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.md +66 -0
- package/docs/server.pathfindabortcallback.md +13 -0
- package/docs/server.pathfindcompletecallback.md +13 -0
- package/docs/server.pathfindingentitycontroller.debug.md +13 -0
- package/docs/server.pathfindingentitycontroller.maxfall.md +13 -0
- package/docs/server.pathfindingentitycontroller.maxjump.md +13 -0
- package/docs/server.pathfindingentitycontroller.maxopensetiterations.md +13 -0
- package/docs/server.pathfindingentitycontroller.md +287 -0
- package/docs/server.pathfindingentitycontroller.pathfind.md +87 -0
- package/docs/server.pathfindingentitycontroller.speed.md +13 -0
- package/docs/server.pathfindingentitycontroller.target.md +13 -0
- package/docs/server.pathfindingentitycontroller.verticalpenalty.md +13 -0
- package/docs/server.pathfindingentitycontroller.waypointnextindex.md +13 -0
- package/docs/server.pathfindingentitycontroller.waypoints.md +13 -0
- package/docs/server.pathfindingentitycontroller.waypointtimeoutms.md +13 -0
- package/docs/server.pathfindingoptions.md +26 -0
- package/docs/server.playerentitycontroller.autocancelmouseleftclick.md +13 -0
- package/docs/server.playerentitycontroller.idleloopedanimations.md +13 -0
- package/docs/server.playerentitycontroller.interactoneshotanimations.md +13 -0
- package/docs/server.playerentitycontroller.jumponeshotanimations.md +13 -0
- package/docs/server.playerentitycontroller.md +114 -0
- package/docs/server.playerentitycontroller.runloopedanimations.md +13 -0
- package/docs/server.playerentitycontroller.walkloopedanimations.md +13 -0
- package/docs/server.playerentitycontrolleroptions.autocancelmouseleftclick.md +13 -0
- package/docs/server.playerentitycontrolleroptions.idleloopedanimations.md +13 -0
- package/docs/server.playerentitycontrolleroptions.interactoneshotanimations.md +13 -0
- package/docs/server.playerentitycontrolleroptions.jumponeshotanimations.md +13 -0
- package/docs/server.playerentitycontrolleroptions.md +114 -0
- package/docs/server.playerentitycontrolleroptions.runloopedanimations.md +13 -0
- package/docs/server.playerentitycontrolleroptions.walkloopedanimations.md +13 -0
- package/docs/server.simpleentitycontroller.jump.md +53 -0
- package/docs/server.simpleentitycontroller.md +14 -0
- package/docs/server.waypointmovecompletecallback.md +15 -0
- package/docs/server.waypointmoveskippedcallback.md +15 -0
- package/examples/ai-agents/.env.example +2 -0
- package/examples/ai-agents/README.md +17 -0
- package/examples/ai-agents/bun.lockb +0 -0
- package/examples/big-world/bun.lockb +0 -0
- package/examples/block-entity/bun.lockb +0 -0
- package/examples/child-entity/bun.lockb +0 -0
- package/examples/child-entity/package-lock.json +30 -0
- package/examples/custom-ui/bun.lockb +0 -0
- package/examples/entity-controller/bun.lockb +0 -0
- package/examples/entity-spawn/bun.lockb +0 -0
- package/examples/hole-in-wall-game/bun.lockb +0 -0
- package/examples/lighting/bun.lockb +0 -0
- package/examples/pathfinding/README.md +3 -0
- package/examples/pathfinding/assets/map.json +25828 -0
- package/examples/pathfinding/bun.lockb +0 -0
- package/examples/pathfinding/index.ts +113 -0
- package/examples/pathfinding/package.json +16 -0
- package/examples/payload-game/bun.lockb +0 -0
- package/examples/wall-dodge-game/bun.lockb +0 -0
- package/examples/wall-dodge-game/package-lock.json +30 -0
- package/examples/zombies-fps/assets/audio/sfx/pistol-shoot-1.mp3 +0 -0
- package/examples/zombies-fps/assets/audio/sfx/pistol-shoot-2.mp3 +0 -0
- package/examples/zombies-fps/assets/audio/sfx/pistol-shoot.mp3 +0 -0
- package/examples/zombies-fps/assets/maps/terrain.json +0 -12
- package/examples/zombies-fps/assets/models/items/pistol.glb +0 -0
- package/examples/zombies-fps/assets/ui/index.html +35 -0
- package/examples/zombies-fps/bun.lockb +0 -0
- package/examples/zombies-fps/classes/GamePlayerEntity.ts +62 -5
- package/examples/zombies-fps/classes/GunEntity.ts +66 -0
- package/examples/zombies-fps/classes/PurchaseBarrierEntity.ts +1 -1
- package/examples/zombies-fps/classes/guns/BulletEntity.ts +0 -0
- package/examples/zombies-fps/classes/guns/PistolEntity.ts +49 -0
- package/examples/zombies-fps/gameConfig.ts +8 -9
- package/examples/zombies-fps/index.ts +5 -1
- package/package.json +1 -1
- package/server.api.json +1006 -8
- package/server.d.ts +156 -0
- package/server.js +87 -87
Binary file
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import {
|
2
|
+
startServer,
|
3
|
+
Entity,
|
4
|
+
PathfindingEntityController,
|
5
|
+
PlayerEntity,
|
6
|
+
Quaternion,
|
7
|
+
RigidBodyType,
|
8
|
+
ColliderShape,
|
9
|
+
} from 'hytopia';
|
10
|
+
|
11
|
+
import worldMap from './assets/map.json';
|
12
|
+
|
13
|
+
startServer(world => {
|
14
|
+
world.loadMap(worldMap);
|
15
|
+
|
16
|
+
// Spawn our zombie that will pathfind around the map
|
17
|
+
const zombie = new Entity({
|
18
|
+
controller: new PathfindingEntityController(),
|
19
|
+
name: 'Zombie',
|
20
|
+
modelUri: 'models/npcs/zombie.gltf',
|
21
|
+
modelLoopedAnimations: [ 'walk' ],
|
22
|
+
modelScale: 0.5,
|
23
|
+
rigidBodyOptions: {
|
24
|
+
enabledRotations: { x: false, y: true, z: false },
|
25
|
+
ccdEnabled: true,
|
26
|
+
},
|
27
|
+
});
|
28
|
+
|
29
|
+
// Spawn somewhere in front of the player
|
30
|
+
zombie.spawn(world, { x: 4, y: 3, z: -6 }, Quaternion.fromEuler(0, 180, 0)); // rotate 180 degrees around Y, facing the player spawn point.
|
31
|
+
|
32
|
+
// Enter /pathfind to make the zombie pathfind to the player
|
33
|
+
let pathfindMarkers: Entity[] = [];
|
34
|
+
world.chatManager.registerCommand('/pathfind', (player, args) => {
|
35
|
+
// Get the entity of the palyer that entered the command
|
36
|
+
const playerEntities = world.entityManager.getPlayerEntitiesByPlayer(player);
|
37
|
+
const playerEntity = playerEntities[0];
|
38
|
+
const playerPosition = playerEntity.position;
|
39
|
+
|
40
|
+
// Remove all existing pathfind markers, which we use in the example just to visualize the path taken.
|
41
|
+
pathfindMarkers.forEach(marker => marker.despawn());
|
42
|
+
pathfindMarkers = [];
|
43
|
+
|
44
|
+
// Pathfind the zombie to the player
|
45
|
+
const pathfindingController = zombie.controller as PathfindingEntityController;
|
46
|
+
|
47
|
+
// .pathfind() is synchronous and returns immediately soon as a path is found or not, but before the entity has reached the target.
|
48
|
+
const succeeded = pathfindingController.pathfind(playerPosition, 3, { // all possible options for example
|
49
|
+
debug: true, // Setting true will console log pathfinding result info
|
50
|
+
maxFall: 10, // The maximum fall distance (in blocks) the entity can fall when considering a path.
|
51
|
+
maxJump: 1, // The maximum height (in blocks) the entity will jump when considering a path, the controller will apply the jumps automatically while pathfinding.
|
52
|
+
maxOpenSetIterations: 100, // A performance optimization value. A lower value will make pathfinding faster but may fail for longer paths, a higher value can make pathfinding take longer but will work better for longer paths.
|
53
|
+
verticalPenalty: -1, // A behavior control. The more negative the value, the more the entity will prefer taking a route that requires jumping and falling, even going out of its way to do so. A more positive value will prefer avoiding jumps and falls unless absolutely necessary.
|
54
|
+
pathfindAbortCallback: () => { // Invoked when maxOpenSetIterations is reached and pathfinding aborts.
|
55
|
+
console.log('Pathfinding aborted');
|
56
|
+
},
|
57
|
+
pathfindCompleteCallback: () => { // Invoked when the entity associated with the PathfindingEntityController finishes pathfinding and is now at the target coordinate.
|
58
|
+
console.log('Pathfinding complete');
|
59
|
+
},
|
60
|
+
waypointMoveCompleteCallback: () => { // Invoked when the entity associated with the PathfindingEntityController finishes moving to a calculate waypoint of its current path.
|
61
|
+
console.log('Waypoint reached');
|
62
|
+
},
|
63
|
+
waypointMoveSkippedCallback: () => { // Invoked when the entity associated with the PathfindingEntityController skips a waypoint because it exceeded the waypointTimeoutMs.
|
64
|
+
console.log('Waypoint skipped');
|
65
|
+
},
|
66
|
+
waypointTimeoutMs: 2000, // The timeout in milliseconds for a waypoint to be considered reached. Defaults to 2000ms divided by the speed of the entity.
|
67
|
+
});
|
68
|
+
|
69
|
+
// .pathfind() will return true if the algorithm found a path and began traversing it.
|
70
|
+
console.log(`Path found successfully?: ${succeeded}`);
|
71
|
+
|
72
|
+
// Spawn visual markers for the path
|
73
|
+
pathfindingController.waypoints.forEach(waypoint => {
|
74
|
+
const pathfindMarker = new Entity({
|
75
|
+
modelUri: 'models/items/cookie.gltf',
|
76
|
+
rigidBodyOptions: {
|
77
|
+
type: RigidBodyType.FIXED,
|
78
|
+
colliders: [
|
79
|
+
{
|
80
|
+
shape: ColliderShape.BLOCK,
|
81
|
+
halfExtents: { x: 0.5, y: 0.5, z: 0.5 },
|
82
|
+
isSensor: true,
|
83
|
+
},
|
84
|
+
],
|
85
|
+
},
|
86
|
+
});
|
87
|
+
|
88
|
+
pathfindMarker.spawn(world, { x: waypoint.x, y: waypoint.y + 0.25, z: waypoint.z });
|
89
|
+
|
90
|
+
pathfindMarkers.push(pathfindMarker);
|
91
|
+
});
|
92
|
+
});
|
93
|
+
|
94
|
+
// Spawn a player entity when a player joins the game.
|
95
|
+
world.onPlayerJoin = player => {
|
96
|
+
const playerEntity = new PlayerEntity({
|
97
|
+
player,
|
98
|
+
name: 'Player',
|
99
|
+
modelUri: 'models/players/player.gltf',
|
100
|
+
modelLoopedAnimations: [ 'idle' ],
|
101
|
+
modelScale: 0.5,
|
102
|
+
});
|
103
|
+
|
104
|
+
world.chatManager.sendPlayerMessage(player, 'To make the zombie pathfind to you, enter: /pathfind', '00FF00');
|
105
|
+
|
106
|
+
playerEntity.spawn(world, { x: 4, y: 3, z: 1 });
|
107
|
+
};
|
108
|
+
|
109
|
+
// Despawn all player entities when a player leaves the game.
|
110
|
+
world.onPlayerLeave = player => {
|
111
|
+
world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
|
112
|
+
};
|
113
|
+
});
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{
|
2
|
+
"name": "pathfinding",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
8
|
+
},
|
9
|
+
"keywords": [],
|
10
|
+
"author": "",
|
11
|
+
"license": "ISC",
|
12
|
+
"dependencies": {
|
13
|
+
"@hytopia.com/assets": "^0.2.5",
|
14
|
+
"hytopia": "^0.1.86"
|
15
|
+
}
|
16
|
+
}
|
Binary file
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"name": "payload-game",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"lockfileVersion": 3,
|
5
|
+
"requires": true,
|
6
|
+
"packages": {
|
7
|
+
"": {
|
8
|
+
"name": "payload-game",
|
9
|
+
"version": "1.0.0",
|
10
|
+
"license": "ISC",
|
11
|
+
"dependencies": {
|
12
|
+
"@hytopia.com/assets": "latest",
|
13
|
+
"hytopia": "latest"
|
14
|
+
}
|
15
|
+
},
|
16
|
+
"node_modules/@hytopia.com/assets": {
|
17
|
+
"version": "0.1.7",
|
18
|
+
"resolved": "https://registry.npmjs.org/@hytopia.com/assets/-/assets-0.1.7.tgz",
|
19
|
+
"integrity": "sha512-gC113cIIVKp97cDX3u9n3ats8qS1kuaKPStQXOf1wNxuRs36yBvHM8NJWArfzzk8SG3ilfSZtc7qMYHod2ngcg=="
|
20
|
+
},
|
21
|
+
"node_modules/hytopia": {
|
22
|
+
"version": "0.1.81",
|
23
|
+
"resolved": "https://registry.npmjs.org/hytopia/-/hytopia-0.1.81.tgz",
|
24
|
+
"integrity": "sha512-F7oKIt/3iPwWdrbgycyZ/t7+SrgOOmS4CvmDFiQYDsk+6cILIadBkq/H4M/YpxL3iqqvZVHFDv13tybM+zolZw==",
|
25
|
+
"bin": {
|
26
|
+
"hytopia": "bin/scripts.js"
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
@@ -126,12 +126,6 @@
|
|
126
126
|
"textureUri": "blocks/iron-ore.png",
|
127
127
|
"isCustom": false
|
128
128
|
},
|
129
|
-
{
|
130
|
-
"id": 22,
|
131
|
-
"name": "lava",
|
132
|
-
"textureUri": "blocks/lava.png",
|
133
|
-
"isCustom": false
|
134
|
-
},
|
135
129
|
{
|
136
130
|
"id": 23,
|
137
131
|
"name": "log",
|
@@ -246,12 +240,6 @@
|
|
246
240
|
"textureUri": "blocks/voidsoil.png",
|
247
241
|
"isCustom": false
|
248
242
|
},
|
249
|
-
{
|
250
|
-
"id": 42,
|
251
|
-
"name": "water-flow",
|
252
|
-
"textureUri": "blocks/water-flow.png",
|
253
|
-
"isCustom": false
|
254
|
-
},
|
255
243
|
{
|
256
244
|
"id": 43,
|
257
245
|
"name": "water-still",
|
Binary file
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<style>
|
2
|
+
.crosshair {
|
3
|
+
position: fixed;
|
4
|
+
top: 50%;
|
5
|
+
left: 50%;
|
6
|
+
transform: translate(-50%, -50%);
|
7
|
+
width: 20px;
|
8
|
+
height: 20px;
|
9
|
+
pointer-events: none;
|
10
|
+
opacity: 0.7;
|
11
|
+
}
|
12
|
+
|
13
|
+
.crosshair::before,
|
14
|
+
.crosshair::after {
|
15
|
+
content: '';
|
16
|
+
position: absolute;
|
17
|
+
background-color: rgba(255, 255, 255, 0.8);
|
18
|
+
}
|
19
|
+
|
20
|
+
.crosshair::before {
|
21
|
+
width: 2px;
|
22
|
+
height: 100%;
|
23
|
+
left: 50%;
|
24
|
+
transform: translateX(-50%);
|
25
|
+
}
|
26
|
+
|
27
|
+
.crosshair::after {
|
28
|
+
width: 100%;
|
29
|
+
height: 2px;
|
30
|
+
top: 50%;
|
31
|
+
transform: translateY(-50%);
|
32
|
+
}
|
33
|
+
</style>
|
34
|
+
|
35
|
+
<div class="crosshair"></div>
|
Binary file
|
@@ -1,30 +1,87 @@
|
|
1
1
|
import {
|
2
|
+
CollisionGroup,
|
2
3
|
Player,
|
4
|
+
PlayerCameraOrientation,
|
3
5
|
PlayerEntity,
|
4
6
|
PlayerCameraMode,
|
7
|
+
PlayerInput,
|
8
|
+
Vector3Like,
|
9
|
+
QuaternionLike,
|
10
|
+
World,
|
11
|
+
Quaternion,
|
12
|
+
PlayerEntityController,
|
5
13
|
} from 'hytopia';
|
6
14
|
|
15
|
+
import PistolEntity from './guns/PistolEntity';
|
16
|
+
import type GunEntity from './guns/GunEntity';
|
17
|
+
|
7
18
|
const BASE_HEALTH = 100;
|
19
|
+
const BASE_MONEY = 10;
|
8
20
|
|
9
21
|
export default class GamePlayerEntity extends PlayerEntity {
|
10
22
|
public health: number;
|
11
23
|
public maxHealth: number;
|
24
|
+
public money: number;
|
25
|
+
private _gun: GunEntity | null = null;
|
26
|
+
|
27
|
+
// Player entities always assign a PlayerController to the entity, so we can safely create a convenience getter
|
28
|
+
public get playerController(): PlayerEntityController {
|
29
|
+
return this.controller as PlayerEntityController;
|
30
|
+
}
|
12
31
|
|
13
32
|
public constructor(player: Player) {
|
14
33
|
super({
|
15
34
|
player,
|
16
35
|
name: 'Player',
|
17
36
|
modelUri: 'models/players/soldier-player.gltf',
|
18
|
-
modelLoopedAnimations: [ 'idle' ],
|
19
37
|
modelScale: 0.5,
|
20
38
|
});
|
39
|
+
|
21
40
|
|
22
|
-
//
|
23
|
-
//
|
24
|
-
|
25
|
-
|
41
|
+
// Prevent mouse left click from being cancelled, required
|
42
|
+
// for auto-fire and semi-auto fire mechanics, etc.
|
43
|
+
this.playerController.autoCancelMouseLeftClick = false;
|
44
|
+
|
45
|
+
// Setup player animations
|
46
|
+
this.playerController.idleLoopedAnimations = [ 'idle_gun_right', 'idle_lower' ];
|
47
|
+
this.playerController.interactOneshotAnimations = [];
|
48
|
+
this.playerController.walkLoopedAnimations = [ 'idle_gun_right', 'walk_lower' ];
|
49
|
+
this.playerController.runLoopedAnimations = [ 'idle_gun_right', 'run_lower' ];
|
50
|
+
this.playerController.onTickWithPlayerInput = this._onTickWithPlayerInput;
|
51
|
+
|
52
|
+
// Setup UI
|
53
|
+
this.player.ui.load('ui/index.html');
|
54
|
+
|
55
|
+
// Setup first person camera
|
56
|
+
this.player.camera.setMode(PlayerCameraMode.FIRST_PERSON);
|
57
|
+
this.player.camera.setModelHiddenNodes([ 'head', 'neck' ]);
|
58
|
+
this.player.camera.setOffset({ x: 0, y: 0.5, z: 0 });
|
59
|
+
this.player.camera.setForwardOffset(0.2);
|
26
60
|
|
61
|
+
// Set base stats
|
27
62
|
this.health = BASE_HEALTH;
|
28
63
|
this.maxHealth = BASE_HEALTH;
|
64
|
+
this.money = BASE_MONEY;
|
65
|
+
}
|
66
|
+
|
67
|
+
public override spawn(world: World, position: Vector3Like, rotation?: QuaternionLike): void {
|
68
|
+
super.spawn(world, position, rotation);
|
69
|
+
|
70
|
+
// Prevent players from colliding, setup appropriate collision groups for invisible walls, etc.
|
71
|
+
this.setCollisionGroupsForSolidColliders({
|
72
|
+
belongsTo: [ CollisionGroup.PLAYER ],
|
73
|
+
collidesWith: [ CollisionGroup.BLOCK, CollisionGroup.ENTITY, CollisionGroup.ENTITY_SENSOR ],
|
74
|
+
});
|
75
|
+
|
76
|
+
// Give player a pistol.
|
77
|
+
this._gun = new PistolEntity({ parent: this });
|
78
|
+
this._gun.spawn(world, { x: 0, y: 0, z: -0.2 }, Quaternion.fromEuler(-90, 0, 0));
|
79
|
+
}
|
80
|
+
|
81
|
+
private _onTickWithPlayerInput = (entity: PlayerEntity, input: PlayerInput, cameraOrientation: PlayerCameraOrientation, deltaTimeMs: number) => {
|
82
|
+
if (input.ml && this._gun) {
|
83
|
+
this._gun.shoot();
|
84
|
+
}
|
29
85
|
}
|
30
86
|
}
|
87
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
import {
|
2
|
+
Entity,
|
3
|
+
EntityOptions,
|
4
|
+
} from 'hytopia';
|
5
|
+
|
6
|
+
export type GunHand = 'left' | 'right' | 'both';
|
7
|
+
|
8
|
+
export interface GunEntityOptions extends EntityOptions {
|
9
|
+
ammo: number; // The amount of ammo in the clip.
|
10
|
+
damage: number; // The damage of the gun.
|
11
|
+
fireRate: number; // Bullets shot per second.
|
12
|
+
reloadTime: number; // Seconds to reload.
|
13
|
+
hand: GunHand; // The hand the weapon is held in.
|
14
|
+
maxAmmo: number; // The amount of ammo the clip can hold.
|
15
|
+
}
|
16
|
+
|
17
|
+
export default class GunEntity extends Entity {
|
18
|
+
public ammo: number;
|
19
|
+
public damage: number;
|
20
|
+
public fireRate: number;
|
21
|
+
public hand: GunHand;
|
22
|
+
public maxAmmo: number;
|
23
|
+
public reloadTime: number;
|
24
|
+
private _lastFireTime: number = 0;
|
25
|
+
|
26
|
+
public constructor(options: GunEntityOptions) {
|
27
|
+
super({
|
28
|
+
...options,
|
29
|
+
parent: options.parent,
|
30
|
+
parentNodeName: options.parent ? GunEntity._getParentNodeName(options.hand) : undefined,
|
31
|
+
});
|
32
|
+
|
33
|
+
this.fireRate = options.fireRate;
|
34
|
+
this.damage = options.damage;
|
35
|
+
this.ammo = options.ammo;
|
36
|
+
this.hand = options.hand;
|
37
|
+
this.maxAmmo = options.maxAmmo;
|
38
|
+
this.reloadTime = options.reloadTime;
|
39
|
+
}
|
40
|
+
|
41
|
+
// simple convenience helper for handling ammo and fire rate in shoot() overrides.
|
42
|
+
public processShoot(): boolean {
|
43
|
+
const now = performance.now();
|
44
|
+
|
45
|
+
if (this._lastFireTime && now - this._lastFireTime < 1000 / this.fireRate) {
|
46
|
+
return false;
|
47
|
+
}
|
48
|
+
|
49
|
+
if (this.ammo <= 0) {
|
50
|
+
//return false;
|
51
|
+
}
|
52
|
+
|
53
|
+
this.ammo--;
|
54
|
+
this._lastFireTime = now;
|
55
|
+
|
56
|
+
return true;
|
57
|
+
}
|
58
|
+
|
59
|
+
// override to create specific gun shoot logic
|
60
|
+
public shoot() {}
|
61
|
+
|
62
|
+
// convenience helper for getting the node name of the hand the gun is held in.
|
63
|
+
private static _getParentNodeName(hand: GunHand): string {
|
64
|
+
return hand === 'left' ? 'hand_left_anchor' : 'hand_right_anchor';
|
65
|
+
}
|
66
|
+
}
|
File without changes
|
@@ -0,0 +1,49 @@
|
|
1
|
+
import {
|
2
|
+
Audio,
|
3
|
+
PlayerEntity,
|
4
|
+
} from 'hytopia';
|
5
|
+
|
6
|
+
import GunEntity from '../GunEntity';
|
7
|
+
import type { GunEntityOptions } from '../GunEntity';
|
8
|
+
|
9
|
+
export default class PistolEntity extends GunEntity {
|
10
|
+
public constructor(options: Partial<GunEntityOptions>) {
|
11
|
+
super({
|
12
|
+
ammo: options.ammo ?? 10,
|
13
|
+
damage: options.damage ?? 3,
|
14
|
+
fireRate: options.fireRate ?? 5,
|
15
|
+
hand: options.hand ?? 'right',
|
16
|
+
maxAmmo: options.maxAmmo ?? 10,
|
17
|
+
modelUri: 'models/items/pistol.glb',
|
18
|
+
modelScale: 1.3,
|
19
|
+
parent: options.parent,
|
20
|
+
reloadTime: options.reloadTime ?? 1,
|
21
|
+
});
|
22
|
+
}
|
23
|
+
|
24
|
+
public override shoot() {
|
25
|
+
if (!this.parent || !this.processShoot()) {
|
26
|
+
return;
|
27
|
+
}
|
28
|
+
|
29
|
+
const parentPlayerEntity = this.parent as PlayerEntity;
|
30
|
+
|
31
|
+
if (!parentPlayerEntity.world) {
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
|
35
|
+
// cancel the input, pistols require click to shoot
|
36
|
+
parentPlayerEntity.player.input.ml = false;
|
37
|
+
|
38
|
+
// play shoot animation
|
39
|
+
parentPlayerEntity.startModelOneshotAnimations([ 'shoot_gun_right' ]);
|
40
|
+
|
41
|
+
(new Audio({
|
42
|
+
position: parentPlayerEntity.position,
|
43
|
+
referenceDistance: 20,
|
44
|
+
uri: 'audio/sfx/pistol-shoot-1.mp3',
|
45
|
+
volume: 1,
|
46
|
+
})).play(parentPlayerEntity.world, true);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
@@ -49,28 +49,27 @@ export const INVISIBLE_WALLS = [
|
|
49
49
|
position: { x: -29, y: 1, z: -23 },
|
50
50
|
halfExtents: { x: 0.5, y: 5, z: 1.5 },
|
51
51
|
}
|
52
|
-
|
53
52
|
]
|
54
53
|
|
55
54
|
export const PURCHASE_BARRIERS = [
|
56
55
|
{
|
57
56
|
name: 'Unlock Theater Room (South)',
|
58
57
|
removalPrice: 100,
|
59
|
-
position: { x: 2, y: 1.5, z: 15 },
|
58
|
+
position: { x: 2.5, y: 1.5, z: 15 },
|
60
59
|
rotation: Quaternion.fromEuler(0, 0, 0),
|
61
60
|
width: 5,
|
62
61
|
},
|
63
62
|
{
|
64
63
|
name: 'Unlock Parlor (South)',
|
65
64
|
removalPrice: 25,
|
66
|
-
position: { x: -8, y: 1.5, z:
|
65
|
+
position: { x: -8, y: 1.5, z: 18.5 },
|
67
66
|
rotation: Quaternion.fromEuler(0, 90, 0),
|
68
67
|
width: 3,
|
69
68
|
},
|
70
69
|
{
|
71
70
|
name: 'Unlock Dining Hall (South)',
|
72
71
|
removalPrice: 50,
|
73
|
-
position: { x:
|
72
|
+
position: { x: 13, y: 1.5, z: 18.5 },
|
74
73
|
rotation: Quaternion.fromEuler(0, 90, 0),
|
75
74
|
width: 3,
|
76
75
|
},
|
@@ -91,35 +90,35 @@ export const PURCHASE_BARRIERS = [
|
|
91
90
|
{
|
92
91
|
name: 'Unlock Art Gallery (South)',
|
93
92
|
removalPrice: 200,
|
94
|
-
position: { x: 26, y: 1.5, z: -2 },
|
93
|
+
position: { x: 26.5, y: 1.5, z: -2 },
|
95
94
|
rotation: Quaternion.fromEuler(0, 0, 0),
|
96
95
|
width: 5,
|
97
96
|
},
|
98
97
|
{
|
99
98
|
name: 'Unlock Kitchen (South)',
|
100
99
|
removalPrice: 200,
|
101
|
-
position: { x: -
|
100
|
+
position: { x: -22, y: 1.5, z: -2 },
|
102
101
|
rotation: Quaternion.fromEuler(0, 0, 0),
|
103
102
|
width: 5,
|
104
103
|
},
|
105
104
|
{
|
106
105
|
name: 'Unlock Vault',
|
107
106
|
removalPrice: 200,
|
108
|
-
position: { x: 0, y: 1.5, z: -26 },
|
107
|
+
position: { x: 0.5, y: 1.5, z: -26 },
|
109
108
|
rotation: Quaternion.fromEuler(0, 0, 0),
|
110
109
|
width: 3,
|
111
110
|
},
|
112
111
|
{
|
113
112
|
name: 'Unlock Treasure Room (West)',
|
114
113
|
removalPrice: 75,
|
115
|
-
position: { x: -
|
114
|
+
position: { x: -15, y: 1.5, z: -19 },
|
116
115
|
rotation: Quaternion.fromEuler(0, 90, 0),
|
117
116
|
width: 5,
|
118
117
|
},
|
119
118
|
{
|
120
119
|
name: 'Unlock Treasure Room (East)',
|
121
120
|
removalPrice: 75,
|
122
|
-
position: { x: 20, y: 1.5, z: -
|
121
|
+
position: { x: 20, y: 1.5, z: -19 },
|
123
122
|
rotation: Quaternion.fromEuler(0, 90, 0),
|
124
123
|
width: 5,
|
125
124
|
},
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { startServer, Collider, ColliderShape } from 'hytopia';
|
1
|
+
import { startServer, Collider, ColliderShape, CollisionGroup } from 'hytopia';
|
2
2
|
import GamePlayerEntity from './classes/GamePlayerEntity';
|
3
3
|
import PurchaseBarrierEntity from './classes/PurchaseBarrierEntity';
|
4
4
|
import { INVISIBLE_WALLS, PURCHASE_BARRIERS } from './gameConfig';
|
@@ -19,6 +19,10 @@ startServer(world => {
|
|
19
19
|
shape: ColliderShape.BLOCK,
|
20
20
|
halfExtents: wall.halfExtents,
|
21
21
|
relativePosition: wall.position, // since this is not attached to a rigid body, relative position is realtive to the world global coordinate space.
|
22
|
+
collisionGroups: {
|
23
|
+
belongsTo: [ CollisionGroup.BLOCK ],
|
24
|
+
collidesWith: [ CollisionGroup.PLAYER ],
|
25
|
+
},
|
22
26
|
});
|
23
27
|
|
24
28
|
wallCollider.addToSimulation(world.simulation);
|