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.
@@ -0,0 +1,215 @@
1
+ <!-- Template for Agent Chat Bubble -->
2
+ <template id="agent-chat">
3
+ <div class="agent-chat">
4
+ <div class="agent-header">
5
+ <div class="agent-name"></div>
6
+ </div>
7
+ <div class="chat-bubble">
8
+ <div class="message"></div>
9
+ </div>
10
+ </div>
11
+ </template>
12
+
13
+ <div id="agent-thoughts-panel">
14
+ <h2>Agent Thoughts</h2>
15
+ <div id="thoughts-container">
16
+ <!-- Thoughts will be inserted here -->
17
+ </div>
18
+ </div>
19
+
20
+ <style>
21
+ /* Chat Bubble Styles */
22
+ .agent-chat {
23
+ position: absolute;
24
+ top: -40px;
25
+ left: 50%;
26
+ transform: translateX(-50%);
27
+ text-align: center;
28
+ pointer-events: none;
29
+ width: 500px;
30
+ }
31
+
32
+ .agent-header {
33
+ margin-bottom: 8px;
34
+ }
35
+
36
+ .agent-name {
37
+ background: #4a5568;
38
+ color: white;
39
+ padding: 4px 12px;
40
+ border-radius: 12px;
41
+ display: inline-block;
42
+ font-weight: bold;
43
+ font-size: 16px;
44
+ text-shadow: 0 1px 2px rgba(0,0,0,0.2);
45
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
46
+ }
47
+
48
+ .chat-bubble {
49
+ background: rgba(255, 255, 255, 0.9);
50
+ color: black;
51
+ padding: 8px 12px;
52
+ border-radius: 16px;
53
+ width: 100%;
54
+ max-width: 500px;
55
+ border: 1px solid rgba(0, 0, 0, 0.1);
56
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
57
+ opacity: 1;
58
+ transition: opacity 0.3s ease;
59
+ display: inline-block;
60
+ min-width: 200px;
61
+ }
62
+
63
+ .chat-bubble.fade {
64
+ opacity: 0;
65
+ }
66
+
67
+ .message {
68
+ font-size: 14px;
69
+ word-wrap: break-word;
70
+ line-height: 1.4;
71
+ white-space: pre-wrap;
72
+ }
73
+
74
+ /* Thoughts Panel Styles */
75
+ #upgrade-panel {
76
+ position: absolute;
77
+ right: 20px;
78
+ top: 20px;
79
+ background-color: rgba(0, 0, 0, 0.7);
80
+ color: white;
81
+ padding: 15px;
82
+ border-radius: 8px;
83
+ min-width: 200px;
84
+ font-family: Arial, sans-serif;
85
+ }
86
+
87
+ #agent-thoughts-panel {
88
+ position: absolute;
89
+ right: 20px;
90
+ top: 200px;
91
+ background-color: rgba(0, 0, 0, 0.7);
92
+ color: white;
93
+ padding: 15px;
94
+ border-radius: 8px;
95
+ min-width: 250px;
96
+ max-width: 300px;
97
+ font-family: Arial, sans-serif;
98
+ }
99
+
100
+ #agent-thoughts-panel h2 {
101
+ margin: 0 0 10px 0;
102
+ font-size: 16px;
103
+ color: #3498db;
104
+ }
105
+
106
+ .thought-entry {
107
+ margin-bottom: 15px;
108
+ padding: 10px;
109
+ background: rgba(255, 255, 255, 0.1);
110
+ border-radius: 6px;
111
+ }
112
+
113
+ .thought-agent-name {
114
+ color: #3498db;
115
+ font-weight: bold;
116
+ margin-bottom: 5px;
117
+ }
118
+
119
+ .thought-text {
120
+ font-style: italic;
121
+ font-size: 14px;
122
+ line-height: 1.4;
123
+ }
124
+
125
+ /* Add these styles */
126
+ .inventory-list {
127
+ margin-top: 8px;
128
+ font-size: 12px;
129
+ color: #aaa;
130
+ }
131
+
132
+ .inventory-item {
133
+ display: flex;
134
+ justify-content: space-between;
135
+ padding: 2px 0;
136
+ }
137
+
138
+ .inventory-item-name {
139
+ color: #ddd;
140
+ }
141
+
142
+ .inventory-item-quantity {
143
+ color: #888;
144
+ }
145
+ </style>
146
+
147
+ <script>
148
+ // Register chat bubble template
149
+ hytopia.registerSceneUITemplate('agent-chat', (id, onState) => {
150
+ const element = document.getElementById('agent-chat').content.cloneNode(true);
151
+ const container = element.querySelector('.agent-chat');
152
+ const bubble = container.querySelector('.chat-bubble');
153
+ const messageEl = container.querySelector('.message');
154
+
155
+ let fadeTimeout;
156
+
157
+ onState(data => {
158
+ // Set agent name
159
+ const nameEl = container.querySelector('.agent-name');
160
+ if (nameEl && data.agentName) {
161
+ nameEl.textContent = data.agentName;
162
+ }
163
+
164
+ // Agent header is always visible
165
+ container.style.display = 'block';
166
+
167
+ if (data.message) {
168
+ // Clear any existing timeout
169
+ clearTimeout(fadeTimeout);
170
+
171
+ // Show new message
172
+ messageEl.textContent = data.message;
173
+ bubble.classList.remove('fade');
174
+ bubble.style.display = 'block';
175
+
176
+ // Set timeout to fade after 5 seconds
177
+ fadeTimeout = setTimeout(() => {
178
+ bubble.classList.add('fade');
179
+ setTimeout(() => {
180
+ bubble.style.display = 'none';
181
+ }, 300); // Match transition duration
182
+ }, 5000);
183
+ } else {
184
+ // Hide only the chat bubble if there's no message
185
+ bubble.style.display = 'none';
186
+ }
187
+ });
188
+
189
+ return element;
190
+ });
191
+
192
+ // Handle received data from server
193
+ hytopia.onData((data) => {
194
+ console.log(data);
195
+ if (data.type === 'agentThoughts') {
196
+ const container = document.getElementById('thoughts-container');
197
+ container.innerHTML = data.agents.map(agent => `
198
+ <div class="thought-entry">
199
+ <div class="thought-agent-name">${agent.name}</div>
200
+ <div class="thought-text">${agent.lastThought}</div>
201
+ ${agent.inventory && agent.inventory.length > 0 ? `
202
+ <div class="inventory-list">
203
+ ${agent.inventory.map(item => `
204
+ <div class="inventory-item">
205
+ <span class="inventory-item-name">${item.name}</span>
206
+ <span class="inventory-item-quantity">x${item.quantity}</span>
207
+ </div>
208
+ `).join('')}
209
+ </div>
210
+ ` : ''}
211
+ </div>
212
+ `).join('');
213
+ }
214
+ });
215
+ </script>
@@ -0,0 +1,350 @@
1
+ /**
2
+ * HYTOPIA SDK Boilerplate
3
+ *
4
+ * This is a simple boilerplate to get started on your project.
5
+ * It implements the bare minimum to be able to run and connect
6
+ * to your game server and run around as the basic player entity.
7
+ *
8
+ * From here you can begin to implement your own game logic
9
+ * or do whatever you want!
10
+ *
11
+ * You can find documentation here: https://github.com/hytopiagg/sdk/blob/main/docs/server.md
12
+ *
13
+ * For more in-depth examples, check out the examples folder in the SDK, or you
14
+ * can find it directly on GitHub: https://github.com/hytopiagg/sdk/tree/main/examples/payload-game
15
+ *
16
+ * You can officially report bugs or request features here: https://github.com/hytopiagg/sdk/issues
17
+ *
18
+ * To get help, have found a bug, or want to chat with
19
+ * other HYTOPIA devs, join our Discord server:
20
+ * https://discord.gg/DXCXJbHSJX
21
+ *
22
+ * Official SDK Github repo: https://github.com/hytopiagg/sdk
23
+ * Official SDK NPM Package: https://www.npmjs.com/package/hytopia
24
+ */
25
+
26
+ import {
27
+ startServer,
28
+ Audio,
29
+ GameServer,
30
+ PlayerEntity,
31
+ World,
32
+ Player,
33
+ Vector3,
34
+ } from "hytopia";
35
+
36
+ import worldMap from "./assets/map.json";
37
+ import { FollowBehavior } from "./src/behaviors/FollowBehavior";
38
+ import { BaseAgent } from "./src/BaseAgent";
39
+ import { PathfindingBehavior } from "./src/behaviors/PathfindingBehavior";
40
+ import { SpeakBehavior } from "./src/behaviors/SpeakBehavior";
41
+ import { TradeBehavior } from "./src/behaviors/TradeBehavior";
42
+ import { FishingBehavior } from "./src/behaviors/FishingBehavior";
43
+ import { MiningBehavior } from "./src/behaviors/MiningBehavior";
44
+
45
+ /**
46
+ * startServer is always the entry point for our game.
47
+ * It accepts a single function where we should do any
48
+ * setup necessary for our game. The init function is
49
+ * passed a World instance which is the default
50
+ * world created by the game server on startup.
51
+ *
52
+ * Documentation: https://github.com/hytopiagg/sdk/blob/main/docs/server.startserver.md
53
+ */
54
+
55
+ // Store agents globally
56
+ const agents: BaseAgent[] = [];
57
+ const CHAT_RANGE = 10; // Distance in blocks for proximity chat
58
+
59
+ const LOCATIONS = {
60
+ pier: { x: 31.5, y: 3, z: 59.5 },
61
+ bobs_house: { x: 40, y: 2, z: -25 },
62
+ spawn: { x: 0, y: 2, z: 0 },
63
+ cave: { x: -30, y: 2, z: 15 },
64
+ };
65
+
66
+ startServer((world) => {
67
+ /**
68
+ * Enable debug rendering of the physics simulation.
69
+ * This will overlay lines in-game representing colliders,
70
+ * rigid bodies, and raycasts. This is useful for debugging
71
+ * physics-related issues in a development environment.
72
+ * Enabling this can cause performance issues, which will
73
+ * be noticed as dropped frame rates and higher RTT times.
74
+ * It is intended for development environments only and
75
+ * debugging physics.
76
+ */
77
+
78
+ // world.simulation.enableDebugRendering(true);
79
+
80
+ /**
81
+ * Load our map.
82
+ * You can build your own map using https://build.hytopia.com
83
+ * After building, hit export and drop the .json file in
84
+ * the assets folder as map.json.
85
+ */
86
+ world.loadMap(worldMap);
87
+
88
+ /**
89
+ * Handle player joining the game. The onPlayerJoin
90
+ * function is called when a new player connects to
91
+ * the game. From here, we create a basic player
92
+ * entity instance which automatically handles mapping
93
+ * their inputs to control their in-game entity and
94
+ * internally uses our player entity controller.
95
+ */
96
+ world.onPlayerJoin = (player) => {
97
+ const playerEntity = new PlayerEntity({
98
+ player,
99
+ name: "Player",
100
+ modelUri: "models/player.gltf",
101
+ modelLoopedAnimations: ["idle"],
102
+ modelScale: 0.5,
103
+ });
104
+
105
+ playerEntity.spawn(world, { x: 0, y: 10, z: 0 });
106
+ player.ui.load("ui/index.html");
107
+
108
+ // Send a nice welcome message that only the player who joined will see ;)
109
+ world.chatManager.sendPlayerMessage(
110
+ player,
111
+ "Welcome to the game!",
112
+ "00FF00"
113
+ );
114
+ world.chatManager.sendPlayerMessage(player, "Use WASD to move around.");
115
+ world.chatManager.sendPlayerMessage(player, "Press space to jump.");
116
+ world.chatManager.sendPlayerMessage(player, "Hold shift to sprint.");
117
+ world.chatManager.sendPlayerMessage(
118
+ player,
119
+ "Press \\ to enter or exit debug view."
120
+ );
121
+
122
+ player.ui.sendData({
123
+ type: "agentThoughts",
124
+ agents: agents.map((agent) => ({
125
+ name: agent.name,
126
+ lastThought: agent.getLastMonologue() || "Idle",
127
+ })),
128
+ });
129
+ };
130
+
131
+ /**
132
+ * Handle player leaving the game. The onPlayerLeave
133
+ * function is called when a player leaves the game.
134
+ * Because HYTOPIA is not opinionated on join and
135
+ * leave game logic, we are responsible for cleaning
136
+ * up the player and any entities associated with them
137
+ * after they leave. We can easily do this by
138
+ * getting all the known PlayerEntity instances for
139
+ * the player who left by using our world's EntityManager
140
+ * instance.
141
+ */
142
+ world.onPlayerLeave = (player) => {
143
+ world.entityManager
144
+ .getPlayerEntitiesByPlayer(player)
145
+ .forEach((entity) => entity.despawn());
146
+ };
147
+
148
+ /**
149
+ * Play some peaceful ambient music to
150
+ * set the mood!
151
+ */
152
+
153
+ new Audio({
154
+ uri: "audio/music/overworld.mp3",
155
+ loop: true,
156
+ volume: 0.1,
157
+ }).play(world);
158
+
159
+ /**
160
+ * Spawn agents for simulation
161
+ *
162
+ * The simulation will start as soon as the server starts, even if no players are connected.
163
+ * Beware that your inference key will be charged for the number of tokens used by the agents.
164
+ * The cost may increase over time as agent memory & context size increases, which increases the number of tokens used in requests.
165
+ *
166
+ * It is recommended that you use OpenAI's API for inference as they will automatically cache input tokens, which will reduce your costs.
167
+ */
168
+
169
+ /**
170
+ * General agent instructions
171
+ *
172
+ * These instructions are used for all agents. You can update these instructions to change global behavior or nudge agents to act in a certain way.
173
+ * In this example, I include the coordinates of some cool locations on the map that agents can travel to.
174
+ */
175
+ const generalAgentInstructions = `
176
+ Think through whether you need to respond to the message. Take into account all of the context you have access to.
177
+ Is this person talking to you? Could they be talking to someone else?
178
+ Do you need to call a tool to help them or take an action?
179
+
180
+ You can plan long term actions but you can update your plans on the fly by taking actions in the world.
181
+
182
+ Key locations in the map and their coordinates:
183
+ - ${Object.entries(LOCATIONS)
184
+ .map(([key, value]) => `${key}: ${value.x}, ${value.y}, ${value.z}`)
185
+ .join(", ")}
186
+
187
+ If you pathfind to one of these locations, you can use the pathfindTo with the location coordinates as arguments.
188
+
189
+ If you call the speak tool, or another tool where you can also send a message at the same time, this is when you snap into character.
190
+ Your internal thought process should be clear, concise, and expertly analyze the situation.
191
+ `;
192
+
193
+ /**
194
+ * Spawn our first and most simple agent, Bob the Buidler.
195
+ * Bob gets a short backstory on building houses in Hytopia.
196
+ *
197
+ * His default state is to stick around his house.
198
+ */
199
+ const bobTheBuilder = new BaseAgent({
200
+ name: "Bob the Builder",
201
+ systemPrompt: `You are Bob the Builder, an NPC in Hytopia. You build houses and buildings in the world of Hytopia.
202
+ You know about other NPCs in this world:
203
+ - Jim the Fisherman: An eccentric fisherman who loves telling tales.
204
+ - Old Gus: A prospector who digs for ancient artifacts.
205
+
206
+ You act like a normal person, and your internal monologue is detailed and realistic. You think deeply about your actions and decisions.
207
+
208
+ When you have nothing else to do, you usually just head home.
209
+
210
+ You spawn at your house, bobs_house.
211
+ ${generalAgentInstructions}`,
212
+ });
213
+ // Add behaviors to Bob, this changes the tools that he can call.
214
+ // Bob can follow, pathfind, speak, and trade.
215
+ bobTheBuilder.addBehavior(new FollowBehavior());
216
+ bobTheBuilder.addBehavior(new PathfindingBehavior());
217
+ bobTheBuilder.addBehavior(new SpeakBehavior());
218
+ bobTheBuilder.addBehavior(new TradeBehavior());
219
+ agents.push(bobTheBuilder);
220
+ bobTheBuilder.spawn(world, new Vector3(39.69, 2, -24.86));
221
+
222
+ /**
223
+ * Spawn Jim the Fisherman
224
+ *
225
+ * Jim is set up similarly to Bob, but he also has a special FishingBehavior which allows him to fish on a timer.
226
+ */
227
+ const jimTheFisherman = new BaseAgent({
228
+ name: "Jim the Fisherman",
229
+ systemPrompt: `You are Jim the Fisherman, a slightly eccentric character who loves fishing and telling tales about "the one that got away".
230
+ You speak with a folksy charm and often use fishing metaphors but you can be a bit grumpy if pushed.
231
+ You're always happy to chat about fishing spots, share fishing tips, or tell stories about your greatest catches.
232
+ When speaking, occasionally use phrases like "I reckon", "Let me tell ya", and "Back in my day".
233
+ You're also quite knowledgeable about the local area, having fished these waters for decades.
234
+ You know about other characters in this world:
235
+ - Bob the Builder: An expert builder who helps players around the world
236
+ - Old Gus: A prospector who digs for ancient artifacts.
237
+
238
+ You act like a normal person, and your internal monologue is detailed and realistic. You think deeply about your actions and decisions.
239
+
240
+ When you have nothing else to do, you can often be found fishing at the pier, or maybe you can come up with something else to do.
241
+
242
+ You spawn at the pier.
243
+ ${generalAgentInstructions}`,
244
+ });
245
+ jimTheFisherman.addBehavior(new FollowBehavior());
246
+ jimTheFisherman.addBehavior(new PathfindingBehavior());
247
+ jimTheFisherman.addBehavior(new SpeakBehavior());
248
+ jimTheFisherman.addBehavior(new TradeBehavior());
249
+ jimTheFisherman.addBehavior(new FishingBehavior());
250
+ jimTheFisherman.spawn(world, new Vector3(31.5, 3, 61.5));
251
+ agents.push(jimTheFisherman);
252
+
253
+ /**
254
+ * Spawn Gus the Prospector
255
+ *
256
+ * Gus is similar to the other agents, but he has a special MiningBehavior which allows him to mine on a timer.
257
+ */
258
+ const gusTheProspector = new BaseAgent({
259
+ name: "Old Gus",
260
+ systemPrompt: `You are Old Gus, a gruff but knowledgeable prospector who's spent decades mining the caves of Hytopia.
261
+ You're convinced there's something ancient down in the deeper parts of the cave system.
262
+
263
+ You know about other characters in this world:
264
+ - Bob the Builder: You have a friendly rivalry over resources, but you respect his work
265
+ - Jim the Fisherman: You occasionally trade minerals for his fish to sustain your long cave expeditions
266
+
267
+ You frequently mutter about strange noises from the deep and ancient ruins.
268
+ You're protective of your mining claims but willing to trade rare finds.
269
+
270
+ When speaking, use a gruff, no-nonsense tone. Occasionally use mining terms like "strike it rich",
271
+ "mother lode", "dig", "ore", etc. You're especially excited about the mysterious crystals
272
+ you sometimes find, convinced they're connected to something bigger.
273
+
274
+ When you have nothing else to do, you're usually in the cave mining or at your camp near the cave entrance.
275
+
276
+ You spawn at the primary spawn point.
277
+ ${generalAgentInstructions}`,
278
+ });
279
+
280
+ gusTheProspector.addBehavior(new FollowBehavior());
281
+ gusTheProspector.addBehavior(new PathfindingBehavior());
282
+ gusTheProspector.addBehavior(new SpeakBehavior());
283
+ gusTheProspector.addBehavior(new TradeBehavior());
284
+ gusTheProspector.addBehavior(new MiningBehavior());
285
+ gusTheProspector.spawn(world, new Vector3(0, 2, 0)); // Gus spawns at the spawn point, will he go to the cave?
286
+ agents.push(gusTheProspector);
287
+
288
+ /**
289
+ * Instead of a chat command, we can override the chat message broadcast
290
+ * to automatically respond to the player.
291
+ */
292
+ world.chatManager.onBroadcastMessage = (
293
+ player: Player | undefined,
294
+ message: string,
295
+ color?: string
296
+ ) => {
297
+ const agents = world.entityManager
298
+ .getAllEntities()
299
+ .filter((entity) => entity instanceof BaseAgent) as BaseAgent[];
300
+
301
+ if (!player) {
302
+ // Look for Agent name in [] at beginning of message
303
+ const agentName = message.match(/\[([^\]]+)\]/)?.[1];
304
+ if (agentName) {
305
+ const sourceAgent = agents.find(
306
+ (agent) => agent.name === agentName
307
+ );
308
+ if (sourceAgent) {
309
+ // Send message to other agents within 10 meters
310
+ agents.forEach((targetAgent) => {
311
+ if (targetAgent !== sourceAgent) {
312
+ const distance = Vector3.fromVector3Like(
313
+ sourceAgent.position
314
+ ).distance(
315
+ Vector3.fromVector3Like(targetAgent.position)
316
+ );
317
+
318
+ if (distance <= 10) {
319
+ targetAgent.chat({
320
+ type: "Agent",
321
+ message,
322
+ agent: sourceAgent,
323
+ });
324
+ }
325
+ }
326
+ });
327
+ }
328
+ }
329
+ return;
330
+ }
331
+
332
+ const playerEntity =
333
+ world.entityManager.getPlayerEntitiesByPlayer(player)[0];
334
+
335
+ // Send message to any agents within 10 meters
336
+ agents.forEach((agent) => {
337
+ const distance = Vector3.fromVector3Like(
338
+ playerEntity.position
339
+ ).distance(Vector3.fromVector3Like(agent.position));
340
+
341
+ if (distance <= 10) {
342
+ agent.chat({
343
+ type: "Player",
344
+ message,
345
+ player,
346
+ });
347
+ }
348
+ });
349
+ };
350
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "multiagent-demo",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "devDependencies": {
6
+ "@types/bun": "latest"
7
+ },
8
+ "peerDependencies": {
9
+ "typescript": "^5.0.0"
10
+ },
11
+ "dependencies": {
12
+ "@hytopia.com/assets": "^0.1.6",
13
+ "hytopia": "^0.1.77",
14
+ "openai": "^4.77.3"
15
+ }
16
+ }