hytopia 0.1.29 → 0.1.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. package/bin/scripts.js +1 -0
  2. package/docs/server.blocktype.md +2 -2
  3. package/docs/server.blocktype.onentitycollision.md +1 -1
  4. package/docs/server.blocktype.onentitycontactforce.md +1 -1
  5. package/docs/server.defaultcharactercontroller.canjump.md +1 -1
  6. package/docs/server.defaultcharactercontroller.canrun.md +1 -1
  7. package/docs/server.defaultcharactercontroller.canwalk.md +1 -1
  8. package/docs/server.defaultcharactercontroller.md +3 -3
  9. package/docs/server.entity.createcustomcharactercontroller.md +2 -2
  10. package/docs/server.entity.md +9 -9
  11. package/docs/server.entity.onblockcollision.md +1 -1
  12. package/docs/server.entity.onblockcontactforce.md +1 -1
  13. package/docs/server.entity.ondespawn.md +1 -1
  14. package/docs/server.entity.onentitycollision.md +1 -1
  15. package/docs/server.entity.onentitycontactforce.md +1 -1
  16. package/docs/server.entity.onspawn.md +1 -1
  17. package/docs/server.entity.ontick.md +1 -1
  18. package/docs/server.entityoptions.createcustomcharactercontroller.md +13 -0
  19. package/docs/server.entityoptions.md +19 -0
  20. package/docs/server.hytopia.blocktype.md +2 -2
  21. package/docs/server.hytopia.blocktype.onentitycollision.md +1 -1
  22. package/docs/server.hytopia.blocktype.onentitycontactforce.md +1 -1
  23. package/docs/server.hytopia.defaultcharactercontroller.canjump.md +1 -1
  24. package/docs/server.hytopia.defaultcharactercontroller.canrun.md +1 -1
  25. package/docs/server.hytopia.defaultcharactercontroller.canwalk.md +1 -1
  26. package/docs/server.hytopia.defaultcharactercontroller.md +3 -3
  27. package/docs/server.hytopia.entity.createcustomcharactercontroller.md +2 -2
  28. package/docs/server.hytopia.entity.md +9 -9
  29. package/docs/server.hytopia.entity.onblockcollision.md +1 -1
  30. package/docs/server.hytopia.entity.onblockcontactforce.md +1 -1
  31. package/docs/server.hytopia.entity.ondespawn.md +1 -1
  32. package/docs/server.hytopia.entity.onentitycollision.md +1 -1
  33. package/docs/server.hytopia.entity.onentitycontactforce.md +1 -1
  34. package/docs/server.hytopia.entity.onspawn.md +1 -1
  35. package/docs/server.hytopia.entity.ontick.md +1 -1
  36. package/docs/server.hytopia.entityoptions.createcustomcharactercontroller.md +13 -0
  37. package/docs/server.hytopia.entityoptions.md +19 -0
  38. package/docs/server.hytopia.moveoptions.md +5 -0
  39. package/docs/server.hytopia.simplecharactercontroller.md +1 -1
  40. package/docs/server.moveoptions.md +5 -0
  41. package/docs/server.simplecharactercontroller.md +1 -1
  42. package/examples/entity-spawn/index.ts +1 -1
  43. package/examples/payload-game/index.ts +46 -104
  44. package/package.json +1 -1
  45. package/server.api.json +142 -60
  46. package/server.d.ts +51 -33
  47. package/server.js +25 -25
  48. package/tsconfig.json +4 -1
  49. package/examples/character-controller/tsconfig.json +0 -27
  50. package/examples/entity-spawn/tsconfig.json +0 -27
  51. package/examples/payload-game/tsconfig.json +0 -27
@@ -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.30",
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": {