hytopia 0.1.29 → 0.1.31

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. package/README.md +3 -26
  2. package/bin/scripts.js +15 -8
  3. package/docs/server.blocktype.md +2 -2
  4. package/docs/server.blocktype.onentitycollision.md +1 -1
  5. package/docs/server.blocktype.onentitycontactforce.md +1 -1
  6. package/docs/server.defaultcharactercontroller.canjump.md +1 -1
  7. package/docs/server.defaultcharactercontroller.canrun.md +1 -1
  8. package/docs/server.defaultcharactercontroller.canwalk.md +1 -1
  9. package/docs/server.defaultcharactercontroller.md +3 -3
  10. package/docs/server.entity.createcustomcharactercontroller.md +2 -2
  11. package/docs/server.entity.md +9 -9
  12. package/docs/server.entity.onblockcollision.md +1 -1
  13. package/docs/server.entity.onblockcontactforce.md +1 -1
  14. package/docs/server.entity.ondespawn.md +1 -1
  15. package/docs/server.entity.onentitycollision.md +1 -1
  16. package/docs/server.entity.onentitycontactforce.md +1 -1
  17. package/docs/server.entity.onspawn.md +1 -1
  18. package/docs/server.entity.ontick.md +1 -1
  19. package/docs/server.entityoptions.createcustomcharactercontroller.md +13 -0
  20. package/docs/server.entityoptions.md +19 -0
  21. package/docs/server.hytopia.blocktype.md +2 -2
  22. package/docs/server.hytopia.blocktype.onentitycollision.md +1 -1
  23. package/docs/server.hytopia.blocktype.onentitycontactforce.md +1 -1
  24. package/docs/server.hytopia.defaultcharactercontroller.canjump.md +1 -1
  25. package/docs/server.hytopia.defaultcharactercontroller.canrun.md +1 -1
  26. package/docs/server.hytopia.defaultcharactercontroller.canwalk.md +1 -1
  27. package/docs/server.hytopia.defaultcharactercontroller.md +3 -3
  28. package/docs/server.hytopia.entity.createcustomcharactercontroller.md +2 -2
  29. package/docs/server.hytopia.entity.md +9 -9
  30. package/docs/server.hytopia.entity.onblockcollision.md +1 -1
  31. package/docs/server.hytopia.entity.onblockcontactforce.md +1 -1
  32. package/docs/server.hytopia.entity.ondespawn.md +1 -1
  33. package/docs/server.hytopia.entity.onentitycollision.md +1 -1
  34. package/docs/server.hytopia.entity.onentitycontactforce.md +1 -1
  35. package/docs/server.hytopia.entity.onspawn.md +1 -1
  36. package/docs/server.hytopia.entity.ontick.md +1 -1
  37. package/docs/server.hytopia.entityoptions.createcustomcharactercontroller.md +13 -0
  38. package/docs/server.hytopia.entityoptions.md +19 -0
  39. package/docs/server.hytopia.moveoptions.md +5 -0
  40. package/docs/server.hytopia.simplecharactercontroller.md +1 -1
  41. package/docs/server.moveoptions.md +5 -0
  42. package/docs/server.simplecharactercontroller.md +1 -1
  43. package/examples/entity-spawn/index.ts +1 -1
  44. package/examples/payload-game/index.ts +46 -104
  45. package/package.json +1 -1
  46. package/server.api.json +142 -60
  47. package/server.d.ts +51 -33
  48. package/server.js +25 -25
  49. package/tsconfig.json +4 -1
  50. package/examples/character-controller/package.json +0 -14
  51. package/examples/character-controller/tsconfig.json +0 -27
  52. package/examples/entity-spawn/package.json +0 -14
  53. package/examples/entity-spawn/tsconfig.json +0 -27
  54. package/examples/payload-game/package.json +0 -14
  55. 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 = (entity: Entity, started: boolean) => { // If the bullet hits an enemy, deal damage if it is a Spider
235
- if (!started || entity.name !== 'Spider') {
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[entity.id!]--;
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 = entity.getMass();
248
+ const mass = otherEntity.getMass();
246
249
  const knockback = 14 * mass;
247
250
 
248
- entity.applyImpulse({
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[entity.id!] <= 0) {
257
+ if (enemyHealth[otherEntity.id!] <= 0) {
255
258
  // YEET the spider before despawning it so it registers leaving the sensor
256
- entity.setTranslation({ x: 0, y: 100, z: 0 });
257
- setTimeout(() => { entity.despawn(); }, 50); // Despawn after a short delay so we step the physics after translating it so leaving the sensor registers.
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.KINEMATIC_VELOCITY,
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
- spider,
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(this: Entity, tickDeltaMs: number) { // Movement logic for the payload
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
- this.stopModelAnimations(Array.from(this.modelLoopedAnimations).filter(v => v !== 'idle'));
420
- this.startModelLoopedAnimations([ 'idle' ]);
424
+ entity.stopModelAnimations(Array.from(entity.modelLoopedAnimations).filter(v => v !== 'idle'));
425
+ entity.startModelLoopedAnimations([ 'idle' ]);
421
426
  } else {
422
- this.stopModelAnimations(Array.from(this.modelLoopedAnimations).filter(v => v !== 'walk'));
423
- this.startModelLoopedAnimations([ 'walk' ]);
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
- const direction = {
433
- x: Math.abs(deltaX) > 0.1 ? Math.sign(deltaX) : 0,
434
- z: Math.abs(deltaZ) > 0.1 ? Math.sign(deltaZ) : 0,
435
- };
433
+ if (!targetWaypointCoordinate) {
434
+ return console.warn('Payload destination reached!! Game won!!');
435
+ }
436
436
 
437
- // Apply rotation to face direction if necessary based on the current target waypoint
438
- const rotation = this.getRotation();
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
- // Apply velocity to move towards target
460
- this.setLinearVelocity({
461
- x: direction.x * speed,
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
- // Check if we're within 3 blocks of the target waypoint, if so move to next waypoint
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
- // Handle movement to target
509
- const currentPosition = entity.getTranslation();
510
- const targetPosition = enemyPathfindingTargets[entityId];
511
- const direction = {
512
- x: targetPosition.x - currentPosition.x,
513
- y: 0, // We only want rotation around Y axis, so ignore vertical difference
514
- z: targetPosition.z - currentPosition.z,
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
- // Calculate facing rotation
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hytopia",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "The HYTOPIA SDK makes it easy for developers to create multiplayer games on the HYTOPIA platform using JavaScript or TypeScript.",
5
5
  "main": "server.js",
6
6
  "bin": {