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.
- package/boilerplate/index.ts +2 -2
- package/docs/server.entitymanager.getallplayerentities.md +3 -39
- package/docs/server.entitymanager.getplayerentitiesbyplayer.md +55 -0
- package/docs/server.entitymanager.md +16 -2
- package/docs/server.gameserver.md +2 -2
- package/docs/{server.gameserver.modelmanager.md → server.gameserver.modelregistry.md} +3 -3
- package/docs/server.md +1 -1
- package/docs/{server.modelmanager.getboundingbox.md → server.modelregistry.getboundingbox.md} +2 -2
- package/docs/server.modelregistry.instance.md +13 -0
- package/docs/{server.modelmanager.md → server.modelregistry.md} +12 -12
- package/docs/server.playermanager.getconnectedplayersbyworld.md +55 -0
- package/docs/server.playermanager.md +14 -0
- package/docs/server.sceneuimanager.getsceneuibyid.md +55 -0
- package/docs/server.sceneuimanager.md +14 -0
- package/examples/ai-agents/README.md +47 -0
- package/examples/ai-agents/assets/map.json +25828 -0
- package/examples/ai-agents/assets/ui/index.html +215 -0
- package/examples/ai-agents/index.ts +350 -0
- package/examples/ai-agents/package.json +16 -0
- package/examples/ai-agents/src/BaseAgent.ts +482 -0
- package/examples/ai-agents/src/behaviors/FishingBehavior.ts +181 -0
- package/examples/ai-agents/src/behaviors/FollowBehavior.ts +171 -0
- package/examples/ai-agents/src/behaviors/MiningBehavior.ts +226 -0
- package/examples/ai-agents/src/behaviors/PathfindingBehavior.ts +435 -0
- package/examples/ai-agents/src/behaviors/SpeakBehavior.ts +50 -0
- package/examples/ai-agents/src/behaviors/TradeBehavior.ts +254 -0
- package/examples/big-world/index.ts +1 -1
- package/examples/block-entity/index.ts +3 -3
- package/examples/custom-ui/index.ts +1 -1
- package/examples/entity-controller/MyEntityController.ts +1 -1
- package/examples/entity-controller/index.ts +1 -1
- package/examples/entity-spawn/index.ts +1 -1
- package/examples/hole-in-wall-game/index.ts +1 -1
- package/examples/lighting/index.ts +1 -1
- package/examples/payload-game/index.ts +1 -1
- package/examples/wall-dodge-game/index.ts +1 -1
- package/package.json +1 -1
- package/server.api.json +181 -38
- package/server.d.ts +31 -21
- package/server.js +19 -19
- 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.
|
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.
|
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.
|
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.
|
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
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
});
|