hytopia 0.1.29 → 0.1.31
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +3 -26
- package/bin/scripts.js +15 -8
- package/docs/server.blocktype.md +2 -2
- package/docs/server.blocktype.onentitycollision.md +1 -1
- package/docs/server.blocktype.onentitycontactforce.md +1 -1
- package/docs/server.defaultcharactercontroller.canjump.md +1 -1
- package/docs/server.defaultcharactercontroller.canrun.md +1 -1
- package/docs/server.defaultcharactercontroller.canwalk.md +1 -1
- package/docs/server.defaultcharactercontroller.md +3 -3
- package/docs/server.entity.createcustomcharactercontroller.md +2 -2
- package/docs/server.entity.md +9 -9
- package/docs/server.entity.onblockcollision.md +1 -1
- package/docs/server.entity.onblockcontactforce.md +1 -1
- package/docs/server.entity.ondespawn.md +1 -1
- package/docs/server.entity.onentitycollision.md +1 -1
- package/docs/server.entity.onentitycontactforce.md +1 -1
- package/docs/server.entity.onspawn.md +1 -1
- package/docs/server.entity.ontick.md +1 -1
- package/docs/server.entityoptions.createcustomcharactercontroller.md +13 -0
- package/docs/server.entityoptions.md +19 -0
- package/docs/server.hytopia.blocktype.md +2 -2
- package/docs/server.hytopia.blocktype.onentitycollision.md +1 -1
- package/docs/server.hytopia.blocktype.onentitycontactforce.md +1 -1
- package/docs/server.hytopia.defaultcharactercontroller.canjump.md +1 -1
- package/docs/server.hytopia.defaultcharactercontroller.canrun.md +1 -1
- package/docs/server.hytopia.defaultcharactercontroller.canwalk.md +1 -1
- package/docs/server.hytopia.defaultcharactercontroller.md +3 -3
- package/docs/server.hytopia.entity.createcustomcharactercontroller.md +2 -2
- package/docs/server.hytopia.entity.md +9 -9
- package/docs/server.hytopia.entity.onblockcollision.md +1 -1
- package/docs/server.hytopia.entity.onblockcontactforce.md +1 -1
- package/docs/server.hytopia.entity.ondespawn.md +1 -1
- package/docs/server.hytopia.entity.onentitycollision.md +1 -1
- package/docs/server.hytopia.entity.onentitycontactforce.md +1 -1
- package/docs/server.hytopia.entity.onspawn.md +1 -1
- package/docs/server.hytopia.entity.ontick.md +1 -1
- package/docs/server.hytopia.entityoptions.createcustomcharactercontroller.md +13 -0
- package/docs/server.hytopia.entityoptions.md +19 -0
- package/docs/server.hytopia.moveoptions.md +5 -0
- package/docs/server.hytopia.simplecharactercontroller.md +1 -1
- package/docs/server.moveoptions.md +5 -0
- package/docs/server.simplecharactercontroller.md +1 -1
- package/examples/entity-spawn/index.ts +1 -1
- package/examples/payload-game/index.ts +46 -104
- package/package.json +1 -1
- package/server.api.json +142 -60
- package/server.d.ts +51 -33
- package/server.js +25 -25
- package/tsconfig.json +4 -1
- package/examples/character-controller/package.json +0 -14
- package/examples/character-controller/tsconfig.json +0 -27
- package/examples/entity-spawn/package.json +0 -14
- package/examples/entity-spawn/tsconfig.json +0 -27
- package/examples/payload-game/package.json +0 -14
- package/examples/payload-game/tsconfig.json +0 -27
@@ -12,6 +12,11 @@ Options for the [SimpleCharacterController.move()](./server.simplecharactercontr
|
|
12
12
|
export type MoveOptions = {
|
13
13
|
moveCallback?: MoveCallback;
|
14
14
|
moveCompleteCallback?: MoveCompleteCallback;
|
15
|
+
moveIgnoreAxes?: {
|
16
|
+
x?: boolean;
|
17
|
+
y?: boolean;
|
18
|
+
z?: boolean;
|
19
|
+
};
|
15
20
|
};
|
16
21
|
```
|
17
22
|
**References:** [MoveCallback](./server.movecallback.md)<!-- -->, [MoveCompleteCallback](./server.movecompletecallback.md)
|
@@ -15,7 +15,7 @@ export default class SimpleCharacterController extends BaseCharacterController
|
|
15
15
|
|
16
16
|
## Remarks
|
17
17
|
|
18
|
-
This class implements simple movement methods that serve as a way to add realistic movement and rotational facing functionality to an entity. This is also a great base to extend for your own more complex character controller that implements things like pathfinding.
|
18
|
+
This class implements simple movement methods that serve as a way to add realistic movement and rotational facing functionality to an entity. This is also a great base to extend for your own more complex character controller that implements things like pathfinding. Compatible with entities that have kinematic or dynamic rigid body types.
|
19
19
|
|
20
20
|
## Example
|
21
21
|
|
@@ -12,6 +12,11 @@ Options for the [SimpleCharacterController.move()](./server.simplecharactercontr
|
|
12
12
|
export type MoveOptions = {
|
13
13
|
moveCallback?: MoveCallback;
|
14
14
|
moveCompleteCallback?: MoveCompleteCallback;
|
15
|
+
moveIgnoreAxes?: {
|
16
|
+
x?: boolean;
|
17
|
+
y?: boolean;
|
18
|
+
z?: boolean;
|
19
|
+
};
|
15
20
|
};
|
16
21
|
```
|
17
22
|
**References:** [MoveCallback](./server.movecallback.md)<!-- -->, [MoveCompleteCallback](./server.movecompletecallback.md)
|
@@ -15,7 +15,7 @@ export default class SimpleCharacterController extends BaseCharacterController
|
|
15
15
|
|
16
16
|
## Remarks
|
17
17
|
|
18
|
-
This class implements simple movement methods that serve as a way to add realistic movement and rotational facing functionality to an entity. This is also a great base to extend for your own more complex character controller that implements things like pathfinding.
|
18
|
+
This class implements simple movement methods that serve as a way to add realistic movement and rotational facing functionality to an entity. This is also a great base to extend for your own more complex character controller that implements things like pathfinding. Compatible with entities that have kinematic or dynamic rigid body types.
|
19
19
|
|
20
20
|
## Example
|
21
21
|
|
@@ -56,7 +56,7 @@ startServer(world => {
|
|
56
56
|
});
|
57
57
|
|
58
58
|
// A simple collision callback that logs when the spider collides with another Entity.
|
59
|
-
spider.onEntityCollision = (otherEntity, started) => {
|
59
|
+
spider.onEntityCollision = (spider, otherEntity, started) => {
|
60
60
|
console.log('spider colliding with', otherEntity.name, started);
|
61
61
|
};
|
62
62
|
|
@@ -23,9 +23,9 @@ import {
|
|
23
23
|
CollisionGroup,
|
24
24
|
DefaultCharacterController,
|
25
25
|
Entity,
|
26
|
-
GameServer,
|
27
26
|
PlayerEntity,
|
28
27
|
RigidBodyType,
|
28
|
+
SimpleCharacterController,
|
29
29
|
World,
|
30
30
|
startServer,
|
31
31
|
} from 'hytopia';
|
@@ -47,9 +47,9 @@ const PAYLOAD_SPAWN_COORDINATE = { x: 1.5, y: 2.6, z: 69.5 };
|
|
47
47
|
const PAYLOAD_PER_PLAYER_SPEED = 1;
|
48
48
|
const PAYLOAD_MAX_SPEED = 5;
|
49
49
|
const PAYLOAD_WAYPOINT_COORDINATES = [
|
50
|
-
{ x: 1.5, z: 1.5 },
|
51
|
-
{ x: 60.5, z: 1.5 },
|
52
|
-
{ x: 60.5, z: -62.5 },
|
50
|
+
{ x: 1.5, y: 0, z: 1.5 },
|
51
|
+
{ x: 60.5, y: 0, z: 1.5 },
|
52
|
+
{ x: 60.5, y: 0, z: -62.5 },
|
53
53
|
];
|
54
54
|
const PLAYER_SPAWN_COORDINATES = [
|
55
55
|
{ x: 4, y: 3, z: 71 },
|
@@ -89,6 +89,9 @@ let targetWaypointCoordinateIndex = 0; // Current waypoint coordinate index for
|
|
89
89
|
startServer(world => { // Perform our game setup logic in the startServer init callback here.
|
90
90
|
const chatManager = world.chatManager;
|
91
91
|
|
92
|
+
// Enable debug rendering
|
93
|
+
world.simulation.enableDebugRendering(true);
|
94
|
+
|
92
95
|
// Load Map
|
93
96
|
world.loadMap(map);
|
94
97
|
|
@@ -225,36 +228,36 @@ function spawnBullet(world: World, coordinate: Vector3, direction: Vector3) {
|
|
225
228
|
},
|
226
229
|
});
|
227
230
|
|
228
|
-
bullet.onBlockCollision = (block: BlockType, started: boolean) => { // If the bullet hits a block, despawn it
|
231
|
+
bullet.onBlockCollision = (bullet: Entity, block: BlockType, started: boolean) => { // If the bullet hits a block, despawn it
|
229
232
|
if (started) {
|
230
233
|
bullet.despawn();
|
231
234
|
}
|
232
235
|
};
|
233
236
|
|
234
|
-
bullet.onEntityCollision = (
|
235
|
-
if (!started ||
|
237
|
+
bullet.onEntityCollision = (bullet: Entity, otherEntity: Entity, started: boolean) => { // If the bullet hits an enemy, deal damage if it is a Spider
|
238
|
+
if (!started || otherEntity.name !== 'Spider') {
|
236
239
|
return;
|
237
240
|
}
|
238
241
|
|
239
|
-
enemyHealth[
|
242
|
+
enemyHealth[otherEntity.id!]--;
|
240
243
|
|
241
244
|
// Apply knockback, the knockback effect is less if the spider is larger, and more if it is smaller
|
242
245
|
// because of how the physics simulation applies forces relative to automatically calculated mass from the spider's
|
243
246
|
// size
|
244
247
|
const bulletDirection = bullet.getDirectionFromRotation();
|
245
|
-
const mass =
|
248
|
+
const mass = otherEntity.getMass();
|
246
249
|
const knockback = 14 * mass;
|
247
250
|
|
248
|
-
|
251
|
+
otherEntity.applyImpulse({
|
249
252
|
x: -bulletDirection.x * knockback,
|
250
253
|
y: 0,
|
251
254
|
z: -bulletDirection.z * knockback,
|
252
255
|
});
|
253
256
|
|
254
|
-
if (enemyHealth[
|
257
|
+
if (enemyHealth[otherEntity.id!] <= 0) {
|
255
258
|
// YEET the spider before despawning it so it registers leaving the sensor
|
256
|
-
|
257
|
-
setTimeout(() => {
|
259
|
+
otherEntity.setTranslation({ x: 0, y: 100, z: 0 });
|
260
|
+
setTimeout(() => { otherEntity.despawn(); }, 50); // Despawn after a short delay so we step the physics after translating it so leaving the sensor registers.
|
258
261
|
}
|
259
262
|
|
260
263
|
bullet.despawn();
|
@@ -280,12 +283,13 @@ function spawnPayloadEntity(world: World) {
|
|
280
283
|
}
|
281
284
|
|
282
285
|
payloadEntity = new Entity({
|
286
|
+
createCustomCharacterController: (entity: Entity) => new SimpleCharacterController(entity),
|
283
287
|
name: 'Payload',
|
284
288
|
modelUri: 'models/payload.gltf',
|
285
289
|
modelScale: 0.7,
|
286
290
|
modelLoopedAnimations: [ 'idle' ],
|
287
291
|
rigidBodyOptions: {
|
288
|
-
type: RigidBodyType.
|
292
|
+
type: RigidBodyType.KINEMATIC_POSITION,
|
289
293
|
colliders: [
|
290
294
|
{
|
291
295
|
shape: ColliderShape.BLOCK,
|
@@ -336,6 +340,7 @@ function spawnSpider(world: World, coordinate: Vector3) {
|
|
336
340
|
const targetPlayers = new Set<PlayerEntity>();
|
337
341
|
|
338
342
|
const spider = new Entity({
|
343
|
+
createCustomCharacterController: (entity: Entity) => new SimpleCharacterController(entity),
|
339
344
|
name: 'Spider',
|
340
345
|
modelUri: 'models/spider.gltf',
|
341
346
|
modelLoopedAnimations: [ 'walk' ],
|
@@ -375,14 +380,14 @@ function spawnSpider(world: World, coordinate: Vector3) {
|
|
375
380
|
},
|
376
381
|
});
|
377
382
|
|
378
|
-
spider.onTick = (tickDeltaMs: number) => onTickPathfindEnemy( // Use our own basic pathfinding function each tick of the game for the enemy
|
379
|
-
|
383
|
+
spider.onTick = (entity: Entity, tickDeltaMs: number) => onTickPathfindEnemy( // Use our own basic pathfinding function each tick of the game for the enemy
|
384
|
+
entity,
|
380
385
|
targetPlayers,
|
381
386
|
baseSpeed * randomScaleMultiplier,
|
382
387
|
tickDeltaMs,
|
383
388
|
);
|
384
389
|
|
385
|
-
spider.onEntityCollision = (entity: Entity, started: boolean) => { // If the spider hits a player, deal damage and apply knockback
|
390
|
+
spider.onEntityCollision = (spider: Entity, entity: Entity, started: boolean) => { // If the spider hits a player, deal damage and apply knockback
|
386
391
|
if (started && entity instanceof PlayerEntity && entity.isSpawned) {
|
387
392
|
const spiderDirection = spider.getDirectionFromRotation();
|
388
393
|
const knockback = 4 * randomScaleMultiplier;
|
@@ -410,70 +415,35 @@ function spawnSpider(world: World, coordinate: Vector3) {
|
|
410
415
|
enemyHealth[spider.id!] = 2 * Math.round(randomScaleMultiplier);
|
411
416
|
}
|
412
417
|
|
413
|
-
function onTickPathfindPayload(
|
418
|
+
function onTickPathfindPayload(entity: Entity) { // Movement logic for the payload
|
414
419
|
const speed = started // Set the payload speed relative to the number of players in the payload sensor
|
415
420
|
? Math.max(Math.min(PAYLOAD_PER_PLAYER_SPEED * payloadPlayerEntityCount, PAYLOAD_MAX_SPEED), 0)
|
416
421
|
: 0;
|
417
422
|
|
418
423
|
if (!speed) { // Play animations based on if its moving or not
|
419
|
-
|
420
|
-
|
424
|
+
entity.stopModelAnimations(Array.from(entity.modelLoopedAnimations).filter(v => v !== 'idle'));
|
425
|
+
entity.startModelLoopedAnimations([ 'idle' ]);
|
421
426
|
} else {
|
422
|
-
|
423
|
-
|
427
|
+
entity.stopModelAnimations(Array.from(entity.modelLoopedAnimations).filter(v => v !== 'walk'));
|
428
|
+
entity.startModelLoopedAnimations([ 'walk' ]);
|
424
429
|
}
|
425
430
|
|
426
|
-
// Calculate direction to target waypoint
|
427
431
|
const targetWaypointCoordinate = PAYLOAD_WAYPOINT_COORDINATES[targetWaypointCoordinateIndex];
|
428
|
-
const currentPosition = this.getTranslation();
|
429
|
-
const deltaX = targetWaypointCoordinate.x - currentPosition.x;
|
430
|
-
const deltaZ = targetWaypointCoordinate.z - currentPosition.z;
|
431
432
|
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
};
|
433
|
+
if (!targetWaypointCoordinate) {
|
434
|
+
return console.warn('Payload destination reached!! Game won!!');
|
435
|
+
}
|
436
436
|
|
437
|
-
|
438
|
-
|
439
|
-
const currentAngle = 2 * Math.atan2(rotation.y, rotation.w);
|
440
|
-
const targetAngle = Math.atan2(direction.x, direction.z) + Math.PI; // Add PI to face forward
|
441
|
-
|
442
|
-
let angleDiff = targetAngle - currentAngle;
|
443
|
-
while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
|
444
|
-
while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
|
445
|
-
|
446
|
-
if (Math.abs(angleDiff) > 0.01) {
|
447
|
-
const rotationStep = (Math.PI / 2) * (tickDeltaMs / 1000) * Math.sign(angleDiff);
|
448
|
-
const actualRotation = Math.abs(rotationStep) > Math.abs(angleDiff) ? angleDiff : rotationStep;
|
449
|
-
const newAngle = currentAngle + actualRotation;
|
450
|
-
|
451
|
-
this.setRotation({
|
452
|
-
x: 0,
|
453
|
-
y: Math.sin(newAngle / 2),
|
454
|
-
z: 0,
|
455
|
-
w: Math.cos(newAngle / 2),
|
456
|
-
});
|
437
|
+
if (!(entity.characterController instanceof SimpleCharacterController)) { // type guard
|
438
|
+
return console.warn('Payload entity does not have a SimpleCharacterController!');
|
457
439
|
}
|
458
440
|
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
y: 0,
|
463
|
-
z: direction.z * speed,
|
441
|
+
entity.characterController.move(targetWaypointCoordinate, speed, {
|
442
|
+
moveCompleteCallback: () => targetWaypointCoordinateIndex++,
|
443
|
+
moveIgnoreAxes: { y: true },
|
464
444
|
});
|
465
445
|
|
466
|
-
|
467
|
-
const distanceX = Math.abs(currentPosition.x - targetWaypointCoordinate.x);
|
468
|
-
const distanceZ = Math.abs(currentPosition.z - targetWaypointCoordinate.z);
|
469
|
-
|
470
|
-
if (distanceX <= 2 && distanceZ <= 2) {
|
471
|
-
if (targetWaypointCoordinateIndex + 1 < PAYLOAD_WAYPOINT_COORDINATES.length) {
|
472
|
-
targetWaypointCoordinateIndex++;
|
473
|
-
} else {
|
474
|
-
console.log('GAME WON!');
|
475
|
-
}
|
476
|
-
}
|
446
|
+
entity.characterController.face(targetWaypointCoordinate, speed / 2);
|
477
447
|
}
|
478
448
|
|
479
449
|
function onTickPathfindEnemy(entity: Entity, targetPlayers: Set<PlayerEntity>, speed: number, _tickDeltaMs: number) {
|
@@ -482,7 +452,6 @@ function onTickPathfindEnemy(entity: Entity, targetPlayers: Set<PlayerEntity>, s
|
|
482
452
|
const entityId = entity.id!;
|
483
453
|
enemyPathfindAccumulators[entityId] ??= 0; // Initialize the accumulator for this enemy if it isn't initialized yet
|
484
454
|
|
485
|
-
// Handle pathfinding
|
486
455
|
if (!enemyPathfindingTargets[entityId] || enemyPathfindAccumulators[entityId] >= PATHFIND_ACCUMULATOR_THRESHOLD) {
|
487
456
|
const targetPlayer = targetPlayers.values().next().value as PlayerEntity | undefined;
|
488
457
|
|
@@ -492,6 +461,7 @@ function onTickPathfindEnemy(entity: Entity, targetPlayers: Set<PlayerEntity>, s
|
|
492
461
|
|
493
462
|
enemyPathfindAccumulators[entityId] -= PATHFIND_ACCUMULATOR_THRESHOLD;
|
494
463
|
|
464
|
+
// Make the spider jump if its close to the target player
|
495
465
|
const currentPosition = entity.getTranslation();
|
496
466
|
const dx = enemyPathfindingTargets[entityId].x - currentPosition.x;
|
497
467
|
const dz = enemyPathfindingTargets[entityId].z - currentPosition.z;
|
@@ -501,46 +471,18 @@ function onTickPathfindEnemy(entity: Entity, targetPlayers: Set<PlayerEntity>, s
|
|
501
471
|
const mass = entity.getMass();
|
502
472
|
entity.applyImpulse({ x: 0, y: (10 * Math.random() + 5) * mass, z: 0 });
|
503
473
|
}
|
504
|
-
}
|
505
|
-
|
506
|
-
enemyPathfindAccumulators[entityId]++;
|
507
474
|
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
// Normalize the direction vector
|
518
|
-
const length = Math.sqrt(direction.x * direction.x + direction.z * direction.z);
|
519
|
-
if (length > 0) {
|
520
|
-
direction.x /= length;
|
521
|
-
direction.z /= length;
|
475
|
+
// Handle Movement
|
476
|
+
if (!(entity.characterController instanceof SimpleCharacterController)) {
|
477
|
+
return console.warn('Enemy entity does not have a SimpleCharacterController!');
|
478
|
+
}
|
479
|
+
|
480
|
+
const targetPosition = enemyPathfindingTargets[entityId];
|
481
|
+
entity.characterController.move(targetPosition, speed, { moveIgnoreAxes: { y: true } });
|
482
|
+
entity.characterController.face(targetPosition, speed / 2);
|
522
483
|
}
|
523
484
|
|
524
|
-
|
525
|
-
const angle = Math.atan2(direction.x, direction.z);
|
526
|
-
const adjustedAngle = angle + Math.PI;
|
527
|
-
const sinHalfAngle = Math.sin(adjustedAngle * 0.5);
|
528
|
-
const cosHalfAngle = Math.cos(adjustedAngle * 0.5);
|
529
|
-
|
530
|
-
entity.setRotation({ x: 0, y: sinHalfAngle, z: 0, w: cosHalfAngle });
|
531
|
-
|
532
|
-
// Calculate movement
|
533
|
-
const currentVelocity = entity.getLinearVelocity();
|
534
|
-
|
535
|
-
if (Math.abs(currentVelocity.x) < speed && Math.abs(currentVelocity.z) < speed) {
|
536
|
-
const movement = {
|
537
|
-
x: direction.x * speed,
|
538
|
-
y: currentVelocity.y,
|
539
|
-
z: direction.z * speed,
|
540
|
-
};
|
541
|
-
|
542
|
-
entity.setLinearVelocity(movement);
|
543
|
-
}
|
485
|
+
enemyPathfindAccumulators[entityId]++;
|
544
486
|
}
|
545
487
|
|
546
488
|
function onTickPlayerMovement(this: DefaultCharacterController, inputState: PlayerInputState, orientationState: PlayerOrientationState, _deltaTimeMs: number) {
|
package/package.json
CHANGED