hytopia 0.1.29 → 0.1.30
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/bin/scripts.js +1 -0
- 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/tsconfig.json +0 -27
- package/examples/entity-spawn/tsconfig.json +0 -27
- 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 = (
|
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