hytopia 0.1.76 → 0.1.78

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.
Files changed (41) hide show
  1. package/boilerplate/index.ts +2 -2
  2. package/docs/server.entitymanager.getallplayerentities.md +3 -39
  3. package/docs/server.entitymanager.getplayerentitiesbyplayer.md +55 -0
  4. package/docs/server.entitymanager.md +16 -2
  5. package/docs/server.gameserver.md +2 -2
  6. package/docs/{server.gameserver.modelmanager.md → server.gameserver.modelregistry.md} +3 -3
  7. package/docs/server.md +1 -1
  8. package/docs/{server.modelmanager.getboundingbox.md → server.modelregistry.getboundingbox.md} +2 -2
  9. package/docs/server.modelregistry.instance.md +13 -0
  10. package/docs/{server.modelmanager.md → server.modelregistry.md} +12 -12
  11. package/docs/server.playermanager.getconnectedplayersbyworld.md +55 -0
  12. package/docs/server.playermanager.md +14 -0
  13. package/docs/server.sceneuimanager.getsceneuibyid.md +55 -0
  14. package/docs/server.sceneuimanager.md +14 -0
  15. package/examples/ai-agents/README.md +47 -0
  16. package/examples/ai-agents/assets/map.json +25828 -0
  17. package/examples/ai-agents/assets/ui/index.html +215 -0
  18. package/examples/ai-agents/index.ts +350 -0
  19. package/examples/ai-agents/package.json +16 -0
  20. package/examples/ai-agents/src/BaseAgent.ts +482 -0
  21. package/examples/ai-agents/src/behaviors/FishingBehavior.ts +181 -0
  22. package/examples/ai-agents/src/behaviors/FollowBehavior.ts +171 -0
  23. package/examples/ai-agents/src/behaviors/MiningBehavior.ts +226 -0
  24. package/examples/ai-agents/src/behaviors/PathfindingBehavior.ts +435 -0
  25. package/examples/ai-agents/src/behaviors/SpeakBehavior.ts +50 -0
  26. package/examples/ai-agents/src/behaviors/TradeBehavior.ts +254 -0
  27. package/examples/big-world/index.ts +1 -1
  28. package/examples/block-entity/index.ts +3 -3
  29. package/examples/custom-ui/index.ts +1 -1
  30. package/examples/entity-controller/MyEntityController.ts +1 -1
  31. package/examples/entity-controller/index.ts +1 -1
  32. package/examples/entity-spawn/index.ts +1 -1
  33. package/examples/hole-in-wall-game/index.ts +1 -1
  34. package/examples/lighting/index.ts +1 -1
  35. package/examples/payload-game/index.ts +1 -1
  36. package/examples/wall-dodge-game/index.ts +1 -1
  37. package/package.json +1 -1
  38. package/server.api.json +181 -38
  39. package/server.d.ts +31 -21
  40. package/server.js +19 -19
  41. package/docs/server.modelmanager.instance.md +0 -13
@@ -0,0 +1,254 @@
1
+ import { World } from "hytopia";
2
+ import { BaseAgent, type AgentBehavior } from "../BaseAgent";
3
+
4
+ interface TradeRequest {
5
+ from: BaseAgent;
6
+ to: BaseAgent;
7
+ offerItems: { name: string; quantity: number }[];
8
+ requestItems: { name: string; quantity: number }[];
9
+ timestamp: number;
10
+ }
11
+ /**
12
+ * Simple implementation of trade behavior for Agents.
13
+ * This is a simple example of how behaviours can have state, in this case, a map of active trade requests.
14
+ * It also demonstrates how Behaviors can manage agent state like the inventory.
15
+ */
16
+ export class TradeBehavior implements AgentBehavior {
17
+ private activeRequests: Map<string, TradeRequest> = new Map();
18
+
19
+ onUpdate(agent: BaseAgent, world: World): void {}
20
+
21
+ private generateTradeId(from: BaseAgent, to: BaseAgent): string {
22
+ return `${from.name}_${to.name}_${Date.now()}`;
23
+ }
24
+
25
+ onToolCall(
26
+ agent: BaseAgent,
27
+ world: World,
28
+ toolName: string,
29
+ args: any
30
+ ): string | void {
31
+ if (toolName === "request_trade") {
32
+ const { target, offer, request } = args;
33
+
34
+ // Find target agent
35
+ const nearbyEntities = agent.getNearbyEntities(5);
36
+ const targetEntity = nearbyEntities.find((e) => e.name === target);
37
+
38
+ if (!targetEntity || targetEntity.type !== "Agent") {
39
+ return `Cannot find ${target} nearby to trade with.`;
40
+ }
41
+
42
+ const targetAgent = world.entityManager
43
+ .getAllEntities()
44
+ .find(
45
+ (e) => e instanceof BaseAgent && e.name === target
46
+ ) as BaseAgent;
47
+
48
+ if (!targetAgent) return "Target agent not found";
49
+
50
+ // Verify agent has the items they're offering
51
+ for (const item of offer) {
52
+ if (!agent.removeFromInventory(item.name, item.quantity)) {
53
+ return `You don't have enough ${item.name} to offer.`;
54
+ }
55
+ // Return items since this is just a check
56
+ agent.addToInventory({
57
+ name: item.name,
58
+ quantity: item.quantity,
59
+ });
60
+ }
61
+
62
+ const tradeId = this.generateTradeId(agent, targetAgent);
63
+ const tradeRequest = {
64
+ from: agent,
65
+ to: targetAgent,
66
+ offerItems: offer,
67
+ requestItems: request,
68
+ timestamp: Date.now(),
69
+ };
70
+
71
+ // Set trade request in both agents' trade behaviors
72
+ this.activeRequests.set(tradeId, tradeRequest);
73
+ const targetTradeBehavior = targetAgent
74
+ .getBehaviors()
75
+ .find((b) => b instanceof TradeBehavior) as TradeBehavior;
76
+ if (targetTradeBehavior) {
77
+ targetTradeBehavior.activeRequests.set(tradeId, tradeRequest);
78
+ }
79
+
80
+ targetAgent.handleEnvironmentTrigger(
81
+ `${agent.name} wants to trade!\n` +
82
+ `Offering: ${offer
83
+ .map(
84
+ (i: { quantity: number; name: string }) =>
85
+ `${i.quantity}x ${i.name}`
86
+ )
87
+ .join(", ")}\n` +
88
+ `Requesting: ${request
89
+ .map(
90
+ (i: { quantity: number; name: string }) =>
91
+ `${i.quantity}x ${i.name}`
92
+ )
93
+ .join(", ")}\n` +
94
+ `Use accept_trade or decline_trade with tradeId: ${tradeId}`
95
+ );
96
+
97
+ return "Trade request sent!";
98
+ } else if (toolName === "accept_trade") {
99
+ const { tradeId } = args;
100
+ const request = this.activeRequests.get(tradeId);
101
+ console.log(`${agent.name} attempting to accept trade ${tradeId}`);
102
+
103
+ if (!request) {
104
+ console.log(`Trade ${tradeId} not found or expired`);
105
+ return "Trade request not found or expired.";
106
+ }
107
+
108
+ if (request.to !== agent) {
109
+ console.log(
110
+ `${agent.name} tried to accept trade meant for ${request.to.name}`
111
+ );
112
+ return "This trade request was not sent to you.";
113
+ }
114
+
115
+ // Verify receiving agent has requested items
116
+ for (const item of request.requestItems) {
117
+ if (!agent.removeFromInventory(item.name, item.quantity)) {
118
+ console.log(
119
+ `${agent.name} lacks required item: ${item.quantity}x ${item.name}`
120
+ );
121
+ return `You don't have enough ${item.name} to complete the trade.`;
122
+ }
123
+
124
+ // Return items since this is just a check
125
+ agent.addToInventory({
126
+ name: item.name,
127
+ quantity: item.quantity,
128
+ });
129
+ }
130
+
131
+ console.log(
132
+ `Executing trade between ${request.from.name} and ${request.to.name}`
133
+ );
134
+ console.log(
135
+ `${request.from.name} offers: ${JSON.stringify(
136
+ request.offerItems
137
+ )}`
138
+ );
139
+ console.log(
140
+ `${request.to.name} offers: ${JSON.stringify(
141
+ request.requestItems
142
+ )}`
143
+ );
144
+
145
+ // Execute the trade
146
+ // Remove items from both agents
147
+ for (const item of request.offerItems) {
148
+ request.from.removeFromInventory(item.name, item.quantity);
149
+ }
150
+ for (const item of request.requestItems) {
151
+ request.to.removeFromInventory(item.name, item.quantity);
152
+ }
153
+
154
+ // Add items to both agents
155
+ for (const item of request.offerItems) {
156
+ request.to.addToInventory({
157
+ name: item.name,
158
+ quantity: item.quantity,
159
+ });
160
+ }
161
+ for (const item of request.requestItems) {
162
+ request.from.addToInventory({
163
+ name: item.name,
164
+ quantity: item.quantity,
165
+ });
166
+ }
167
+
168
+ // Clear trade request from both agents' trade behaviors
169
+ this.activeRequests.delete(tradeId);
170
+ const fromTradeBehavior = request.from
171
+ .getBehaviors()
172
+ .find((b) => b instanceof TradeBehavior) as TradeBehavior;
173
+ if (fromTradeBehavior) {
174
+ fromTradeBehavior.activeRequests.delete(tradeId);
175
+ }
176
+
177
+ console.log(`Trade ${tradeId} completed successfully`);
178
+
179
+ // Notify both agents
180
+ request.from.handleEnvironmentTrigger(
181
+ `${agent.name} accepted your trade!`
182
+ );
183
+ request.to.handleEnvironmentTrigger(
184
+ `You accepted the trade with ${agent.name}!`
185
+ );
186
+ return "Trade completed successfully!";
187
+ } else if (toolName === "decline_trade") {
188
+ const { tradeId } = args;
189
+ const request = this.activeRequests.get(tradeId);
190
+
191
+ if (!request) {
192
+ console.log("Trade request not found or expired.");
193
+ return "Trade request not found or expired.";
194
+ }
195
+
196
+ if (request.to !== agent) {
197
+ console.log("This trade request was not sent to you.");
198
+ return "This trade request was not sent to you.";
199
+ }
200
+
201
+ // Clear trade request from both agents' trade behaviors
202
+ this.activeRequests.delete(tradeId);
203
+ const fromTradeBehavior = request.from
204
+ .getBehaviors()
205
+ .find((b) => b instanceof TradeBehavior) as TradeBehavior;
206
+ if (fromTradeBehavior) {
207
+ fromTradeBehavior.activeRequests.delete(tradeId);
208
+ }
209
+
210
+ request.from.handleEnvironmentTrigger(
211
+ `${agent.name} declined your trade.`
212
+ );
213
+ console.log("Trade declined.");
214
+ return "Trade declined.";
215
+ }
216
+ }
217
+
218
+ getPromptInstructions(): string {
219
+ return `
220
+ To request a trade with another agent:
221
+ <action type="request_trade">
222
+ {
223
+ "target": "name of agent to trade with",
224
+ "offer": [{ "name": "item name", "quantity": number }],
225
+ "request": [{ "name": "item name", "quantity": number }]
226
+ }
227
+ </action>
228
+
229
+ To accept a trade request:
230
+ <action type="accept_trade">
231
+ {
232
+ "tradeId": "trade_id_from_request"
233
+ }
234
+ </action>
235
+
236
+ To decline a trade request:
237
+ <action type="decline_trade">
238
+ {
239
+ "tradeId": "trade_id_from_request"
240
+ }
241
+ </action>
242
+
243
+ Trading requires both agents to be within 5 meters of each other.
244
+ Both agents must have the required items in their inventory.
245
+
246
+ If someone verbally offers a trade, but you don't get the official request from the Environment, you should ask them to request the trade so you can accept it.
247
+
248
+ If you request to trade with an agent, they will need to accept the trade request.`;
249
+ }
250
+
251
+ getState(): string {
252
+ return `Active trade requests: ${JSON.stringify(this.activeRequests)}`;
253
+ }
254
+ }
@@ -36,6 +36,6 @@ startServer(world => {
36
36
 
37
37
  // Despawn all player entities when a player leaves the game.
38
38
  world.onPlayerLeave = player => {
39
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
39
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
40
40
  };
41
41
  });
@@ -30,7 +30,7 @@ startServer(world => {
30
30
  };
31
31
 
32
32
  world.onPlayerLeave = player => {
33
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
33
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
34
34
  };
35
35
 
36
36
  /**
@@ -41,7 +41,7 @@ startServer(world => {
41
41
  blockHalfExtents: { x: 1, y: 0.5, z: 1 },
42
42
  rigidBodyOptions: {
43
43
  type: RigidBodyType.KINEMATIC_VELOCITY, // Kinematic means platform won't be effected by external physics, including gravity
44
- linearVelocity: { x: 0, y: 0, z: 3 }, // A starting velocity that won't change because it's kinematic
44
+ linearVelocity: { x: 0, y: 0, z: 3 }, // A starting velocity that won't change until we change it, because it's kinematic
45
45
  },
46
46
  });
47
47
 
@@ -162,7 +162,7 @@ startServer(world => {
162
162
  const targetPlayer = connectedPlayers[Math.floor(Math.random() * connectedPlayers.length)];
163
163
 
164
164
  // get the player's entity
165
- const targetPlayerEntity = world.entityManager.getAllPlayerEntities(targetPlayer)[0];
165
+ const targetPlayerEntity = world.entityManager.getPlayerEntitiesByPlayer(targetPlayer)[0];
166
166
 
167
167
  if (!targetPlayerEntity) { return; } // if the player doesn't have an entity, don't pathfind.
168
168
 
@@ -51,7 +51,7 @@ startServer(world => {
51
51
  };
52
52
 
53
53
  world.onPlayerLeave = player => {
54
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
54
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
55
55
  // Remove the player entity from our map for our list.
56
56
  playerEntityMap.delete(player);
57
57
  };
@@ -144,7 +144,7 @@ export default class MyEntityController extends BaseEntityController {
144
144
  collidesWith: [ CollisionGroup.BLOCK, CollisionGroup.ENTITY ],
145
145
  },
146
146
  isSensor: true,
147
- relativePosition: { x: 0, y: -0.75, z: 0 },
147
+ position: { x: 0, y: -0.75, z: 0 },
148
148
  tag: 'groundSensor',
149
149
  onCollision: (_other: BlockType | Entity, started: boolean) => {
150
150
  // Ground contact
@@ -33,6 +33,6 @@ startServer(world => {
33
33
  };
34
34
 
35
35
  world.onPlayerLeave = player => {
36
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
36
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
37
37
  };
38
38
  });
@@ -80,6 +80,6 @@ function setup(world: World) {
80
80
 
81
81
  // Despawn all player entities when a player leaves the game.
82
82
  world.onPlayerLeave = player => {
83
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
83
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
84
84
  };
85
85
  }
@@ -115,7 +115,7 @@ function onPlayerJoin(world: World, player: Player) {
115
115
  }
116
116
 
117
117
  function onPlayerLeave(world: World, player: Player) {
118
- world.entityManager.getAllPlayerEntities(player).forEach(entity => {
118
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => {
119
119
  removePlayerFromQueue(entity);
120
120
  killPlayer(entity);
121
121
  entity.despawn();
@@ -109,7 +109,7 @@ startServer(world => {
109
109
 
110
110
  // Despawn all player entities when a player leaves the game.
111
111
  world.onPlayerLeave = player => {
112
- world.entityManager.getAllPlayerEntities(player).forEach(entity => entity.despawn());
112
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => entity.despawn());
113
113
  };
114
114
 
115
115
  // Play some music on game start
@@ -151,7 +151,7 @@ startServer(world => { // Perform our game setup logic in the startServer init c
151
151
  // We apply a translation prior to despawn because of a bug in the RAPIER
152
152
  // physics engine we use where entities despawned to not trigger a collision
153
153
  // event for leaving a sensor. This is a workaround till a better solution is found.
154
- world.entityManager.getAllPlayerEntities(player).forEach(entity => {
154
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => {
155
155
  entity.setPosition({ x: 0, y: 100, z: 0 });
156
156
  setTimeout(() => entity.despawn(), 50); // Despawn after a short delay so we step the physics after translating it so leaving the sensor registers.
157
157
  });
@@ -277,7 +277,7 @@ function onPlayerJoin(world: World, player: Player) {
277
277
  * cleanup when they leave the game.
278
278
  */
279
279
  function onPlayerLeave(world: World, player: Player) {
280
- world.entityManager.getAllPlayerEntities(player).forEach(entity => {
280
+ world.entityManager.getPlayerEntitiesByPlayer(player).forEach(entity => {
281
281
  endGame(entity); // explicitly end their game if they leave
282
282
  entity.despawn(); // despawn their entity
283
283
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hytopia",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
4
4
  "description": "The HYTOPIA SDK makes it easy for developers to create massively multiplayer games using JavaScript or TypeScript.",
5
5
  "main": "server.js",
6
6
  "bin": {