hytopia 0.1.77 → 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/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/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/entity-controller/MyEntityController.ts +1 -1
- package/package.json +1 -1
- package/server.api.json +15 -15
- package/server.d.ts +11 -19
- package/server.js +1 -1
- 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
|
+
}
|
@@ -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
|
package/package.json
CHANGED
package/server.api.json
CHANGED
@@ -13664,17 +13664,17 @@
|
|
13664
13664
|
},
|
13665
13665
|
{
|
13666
13666
|
"kind": "Property",
|
13667
|
-
"canonicalReference": "server!GameServer#
|
13667
|
+
"canonicalReference": "server!GameServer#modelRegistry:member",
|
13668
13668
|
"docComment": "/**\n * The model manager for the game server.\n */\n",
|
13669
13669
|
"excerptTokens": [
|
13670
13670
|
{
|
13671
13671
|
"kind": "Content",
|
13672
|
-
"text": "get
|
13672
|
+
"text": "get modelRegistry(): "
|
13673
13673
|
},
|
13674
13674
|
{
|
13675
13675
|
"kind": "Reference",
|
13676
|
-
"text": "
|
13677
|
-
"canonicalReference": "server!
|
13676
|
+
"text": "ModelRegistry",
|
13677
|
+
"canonicalReference": "server!ModelRegistry:class"
|
13678
13678
|
},
|
13679
13679
|
{
|
13680
13680
|
"kind": "Content",
|
@@ -13684,7 +13684,7 @@
|
|
13684
13684
|
"isReadonly": true,
|
13685
13685
|
"isOptional": false,
|
13686
13686
|
"releaseTag": "Public",
|
13687
|
-
"name": "
|
13687
|
+
"name": "modelRegistry",
|
13688
13688
|
"propertyTypeTokenRange": {
|
13689
13689
|
"startIndex": 1,
|
13690
13690
|
"endIndex": 2
|
@@ -16691,23 +16691,23 @@
|
|
16691
16691
|
},
|
16692
16692
|
{
|
16693
16693
|
"kind": "Class",
|
16694
|
-
"canonicalReference": "server!
|
16695
|
-
"docComment": "/**\n * Manages model data for all known models of the game.\n *\n * @remarks\n *\n * The
|
16694
|
+
"canonicalReference": "server!ModelRegistry:class",
|
16695
|
+
"docComment": "/**\n * Manages model data for all known models of the game.\n *\n * @remarks\n *\n * The ModelRegistry is created internally as a global singletone accessible with the static property `ModelRegistry.instance`.\n *\n * The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `ModelRegistry` class.\n *\n * @example\n * ```typescript\n * import { ModelRegistry } from 'hytopia';\n *\n * const modelRegistry = ModelRegistry.instance;\n * const boundingBox = modelRegistry.getBoundingBox('models/player.gltf');\n * ```\n *\n * @public\n */\n",
|
16696
16696
|
"excerptTokens": [
|
16697
16697
|
{
|
16698
16698
|
"kind": "Content",
|
16699
|
-
"text": "export default class
|
16699
|
+
"text": "export default class ModelRegistry "
|
16700
16700
|
}
|
16701
16701
|
],
|
16702
|
-
"fileUrlPath": "src/models/
|
16702
|
+
"fileUrlPath": "src/models/ModelRegistry.ts",
|
16703
16703
|
"releaseTag": "Public",
|
16704
16704
|
"isAbstract": false,
|
16705
|
-
"name": "
|
16705
|
+
"name": "ModelRegistry",
|
16706
16706
|
"preserveMemberOrder": false,
|
16707
16707
|
"members": [
|
16708
16708
|
{
|
16709
16709
|
"kind": "Method",
|
16710
|
-
"canonicalReference": "server!
|
16710
|
+
"canonicalReference": "server!ModelRegistry#getBoundingBox:member(1)",
|
16711
16711
|
"docComment": "/**\n * Retrieves the bounding box of a model.\n *\n * @param modelUri - The URI of the model to retrieve the bounding box for.\n *\n * @returns The bounding box of the model.\n */\n",
|
16712
16712
|
"excerptTokens": [
|
16713
16713
|
{
|
@@ -16756,8 +16756,8 @@
|
|
16756
16756
|
},
|
16757
16757
|
{
|
16758
16758
|
"kind": "Property",
|
16759
|
-
"canonicalReference": "server!
|
16760
|
-
"docComment": "/**\n * The global
|
16759
|
+
"canonicalReference": "server!ModelRegistry.instance:member",
|
16760
|
+
"docComment": "/**\n * The global ModelRegistry instance as a singleton.\n */\n",
|
16761
16761
|
"excerptTokens": [
|
16762
16762
|
{
|
16763
16763
|
"kind": "Content",
|
@@ -16765,8 +16765,8 @@
|
|
16765
16765
|
},
|
16766
16766
|
{
|
16767
16767
|
"kind": "Reference",
|
16768
|
-
"text": "
|
16769
|
-
"canonicalReference": "server!
|
16768
|
+
"text": "ModelRegistry",
|
16769
|
+
"canonicalReference": "server!ModelRegistry:class"
|
16770
16770
|
},
|
16771
16771
|
{
|
16772
16772
|
"kind": "Content",
|
package/server.d.ts
CHANGED
@@ -1768,7 +1768,7 @@ export declare class GameServer {
|
|
1768
1768
|
/** The singleton instance of the game server. */
|
1769
1769
|
static get instance(): GameServer;
|
1770
1770
|
/** The model manager for the game server. */
|
1771
|
-
get
|
1771
|
+
get modelRegistry(): ModelRegistry;
|
1772
1772
|
/** The player manager for the game server. */
|
1773
1773
|
get playerManager(): PlayerManager;
|
1774
1774
|
|
@@ -2077,39 +2077,31 @@ export declare enum LightType {
|
|
2077
2077
|
|
2078
2078
|
/** A bounding box for a model. @public */
|
2079
2079
|
declare type ModelBoundingBox = {
|
2080
|
-
min:
|
2081
|
-
|
2082
|
-
y: number;
|
2083
|
-
z: number;
|
2084
|
-
};
|
2085
|
-
max: {
|
2086
|
-
x: number;
|
2087
|
-
y: number;
|
2088
|
-
z: number;
|
2089
|
-
};
|
2080
|
+
min: Vector3Like;
|
2081
|
+
max: Vector3Like;
|
2090
2082
|
};
|
2091
2083
|
|
2092
2084
|
/**
|
2093
2085
|
* Manages model data for all known models of the game.
|
2094
2086
|
*
|
2095
2087
|
* @remarks
|
2096
|
-
* The
|
2088
|
+
* The ModelRegistry is created internally as a global
|
2097
2089
|
* singletone accessible with the static property
|
2098
|
-
* `
|
2090
|
+
* `ModelRegistry.instance`.
|
2099
2091
|
*
|
2100
2092
|
* @example
|
2101
2093
|
* ```typescript
|
2102
|
-
* import {
|
2094
|
+
* import { ModelRegistry } from 'hytopia';
|
2103
2095
|
*
|
2104
|
-
* const
|
2105
|
-
* const boundingBox =
|
2096
|
+
* const modelRegistry = ModelRegistry.instance;
|
2097
|
+
* const boundingBox = modelRegistry.getBoundingBox('models/player.gltf');
|
2106
2098
|
* ```
|
2107
2099
|
*
|
2108
2100
|
* @public
|
2109
2101
|
*/
|
2110
|
-
export declare class
|
2111
|
-
/** The global
|
2112
|
-
static readonly instance:
|
2102
|
+
export declare class ModelRegistry {
|
2103
|
+
/** The global ModelRegistry instance as a singleton. */
|
2104
|
+
static readonly instance: ModelRegistry;
|
2113
2105
|
|
2114
2106
|
|
2115
2107
|
|