Jofthomas HF staff commited on
Commit
b399f78
1 Parent(s): 8eade23

revert to main in github

Browse files
patches/convex/aiTown/agentOperations.ts CHANGED
@@ -128,21 +128,21 @@ export const agentDoSomething = internalAction({
128
  return;
129
  } else {
130
  // TODO: have LLM choose the activity & emoji
131
- const activity = ACTIVITIES[Math.floor(Math.random() * ACTIVITIES.length)];
132
- await sleep(Math.random() * 1000);
133
- await ctx.runMutation(api.aiTown.main.sendInput, {
134
- worldId: args.worldId,
135
- name: 'finishDoSomething',
136
- args: {
137
- operationId: args.operationId,
138
- agentId: agent.id,
139
- activity: {
140
- description: activity.description,
141
- emoji: activity.emoji,
142
- until: Date.now() + activity.duration,
143
- },
144
- },
145
- });
146
  return;
147
  }
148
  }
 
128
  return;
129
  } else {
130
  // TODO: have LLM choose the activity & emoji
131
+ // const activity = ACTIVITIES[Math.floor(Math.random() * ACTIVITIES.length)];
132
+ // await sleep(Math.random() * 1000);
133
+ // await ctx.runMutation(api.aiTown.main.sendInput, {
134
+ // worldId: args.worldId,
135
+ // name: 'finishDoSomething',
136
+ // args: {
137
+ // operationId: args.operationId,
138
+ // agentId: agent.id,
139
+ // activity: {
140
+ // description: activity.description,
141
+ // emoji: activity.emoji,
142
+ // until: Date.now() + activity.duration,
143
+ // },
144
+ // },
145
+ // });
146
  return;
147
  }
148
  }
patches/convex/aiTown/gameCycle.ts CHANGED
@@ -115,11 +115,18 @@ const onStateChange = (prevState: CycleState, newState: CycleState, game: Game,
115
  })
116
  };
117
  if (prevState === 'PlayerKillVoting') {
118
- const mostVotedPlayer = processVotes(game.world.gameVotes, [...game.world.players.values()])[0];
119
- const playerToKill = game.world.players.get(mostVotedPlayer.playerId);
120
- console.log(`killing: ${playerToKill?.id}, with ${game.world.gameVotes.length} votes`)
121
- if (playerToKill) {
122
- playerToKill.kill(game, now);
 
 
 
 
 
 
 
123
  }
124
  game.world.gameVotes = [];
125
  }
 
115
  })
116
  };
117
  if (prevState === 'PlayerKillVoting') {
118
+ const werewolves = [...game.world.players.values()].filter((were) => {
119
+ game.playerDescriptions.get(were.id)?.type === 'werewolf'
120
+ })
121
+ if (werewolves.length != 0) {
122
+ const mostVotedPlayer = processVotes(game.world.gameVotes, [...game.world.players.values()])[0];
123
+ const playerToKill = game.world.players.get(mostVotedPlayer.playerId);
124
+ console.log(`killing: ${playerToKill?.id}, with ${game.world.gameVotes.length} votes`)
125
+ if (playerToKill) {
126
+ playerToKill.kill(game, now);
127
+ }
128
+ } else {
129
+ console.log('no werewolves, nobody was killed')
130
  }
131
  game.world.gameVotes = [];
132
  }
patches/convex/util/llm.ts CHANGED
@@ -1,14 +1,22 @@
1
- // That's right! No imports and no dependencies 🤯
2
 
3
  export const LLM_CONFIG = {
 
 
 
 
 
 
 
 
 
4
  /* Ollama (local) config:
5
  */
6
- ollama: true,
7
- url: 'http://127.0.0.1:11434',
8
- chatModel: 'llama3' as const,
9
- embeddingModel: 'mxbai-embed-large',
10
- embeddingDimension: 1024,
11
- stopWords: ['<|eot_id|>'],
12
  // embeddingModel: 'llama3',
13
  // embeddingDimension: 4096,
14
 
@@ -18,7 +26,6 @@ export const LLM_CONFIG = {
18
  chatModel: 'meta-llama/Llama-3-8b-chat-hf',
19
  embeddingModel: 'togethercomputer/m2-bert-80M-8k-retrieval',
20
  embeddingDimension: 768,
21
- stopWords: ['<|eot_id|>'],
22
  */
23
 
24
  /* OpenAI config:
@@ -37,10 +44,10 @@ function apiUrl(path: string) {
37
  process.env.OLLAMA_HOST ??
38
  process.env.OPENAI_API_BASE ??
39
  LLM_CONFIG.url;
40
- if (host.endsWith('/') && path.startsWith('/')) {
41
  return host + path.slice(1);
42
- } else if (!host.endsWith('/') && !path.startsWith('/')) {
43
- return host + '/' + path;
44
  } else {
45
  return host + path;
46
  }
@@ -53,72 +60,67 @@ function apiKey() {
53
  const AuthHeaders = (): Record<string, string> =>
54
  apiKey()
55
  ? {
56
- Authorization: 'Bearer ' + apiKey(),
57
  }
58
  : {};
59
 
60
  // Overload for non-streaming
61
  export async function chatCompletion(
62
- body: Omit<CreateChatCompletionRequest, 'model'> & {
63
- model?: CreateChatCompletionRequest['model'];
64
  } & {
65
  stream?: false | null | undefined;
66
- },
67
  ): Promise<{ content: string; retries: number; ms: number }>;
68
  // Overload for streaming
69
  export async function chatCompletion(
70
- body: Omit<CreateChatCompletionRequest, 'model'> & {
71
- model?: CreateChatCompletionRequest['model'];
72
  } & {
73
  stream?: true;
74
- },
75
  ): Promise<{ content: ChatCompletionContent; retries: number; ms: number }>;
76
  export async function chatCompletion(
77
- body: Omit<CreateChatCompletionRequest, 'model'> & {
78
- model?: CreateChatCompletionRequest['model'];
79
- },
80
  ) {
81
  assertApiKey();
82
  // OLLAMA_MODEL is legacy
83
  body.model =
84
- body.model ?? process.env.LLM_MODEL ?? process.env.OLLAMA_MODEL ?? LLM_CONFIG.chatModel;
85
- const stopWords = body.stop ? (typeof body.stop === 'string' ? [body.stop] : body.stop) : [];
86
- if (LLM_CONFIG.stopWords) stopWords.push(...LLM_CONFIG.stopWords);
87
- console.log(body);
 
 
 
 
 
 
 
88
  const {
89
  result: content,
90
  retries,
91
  ms,
92
  } = await retryWithBackoff(async () => {
93
- const result = await fetch(apiUrl('/v1/chat/completions'), {
94
- method: 'POST',
95
- headers: {
96
- 'Content-Type': 'application/json',
97
- ...AuthHeaders(),
98
- },
99
-
100
- body: JSON.stringify(body),
101
- });
102
- if (!result.ok) {
103
- const error = await result.text();
104
- console.error({ error });
105
- if (result.status === 404 && LLM_CONFIG.ollama) {
106
- await tryPullOllama(body.model!, error);
107
- }
108
- throw {
109
- retry: result.status === 429 || result.status >= 500,
110
- error: new Error(`Chat completion failed with code ${result.status}: ${error}`),
111
- };
112
- }
113
  if (body.stream) {
114
- return new ChatCompletionContent(result.body!, stopWords);
 
 
 
115
  } else {
116
- const json = (await result.json()) as CreateChatCompletionResponse;
117
- const content = json.choices[0].message?.content;
 
 
118
  if (content === undefined) {
119
- throw new Error('Unexpected result from OpenAI: ' + JSON.stringify(json));
 
 
120
  }
121
- console.log(content);
122
  return content;
123
  }
124
  });
@@ -131,17 +133,20 @@ export async function chatCompletion(
131
  }
132
 
133
  export async function tryPullOllama(model: string, error: string) {
134
- if (error.includes('try pulling')) {
135
- console.error('Embedding model not found, pulling from Ollama');
136
- const pullResp = await fetch(apiUrl('/api/pull'), {
137
- method: 'POST',
138
  headers: {
139
- 'Content-Type': 'application/json',
140
  },
141
  body: JSON.stringify({ name: model }),
142
  });
143
- console.log('Pull response', await pullResp.text());
144
- throw { retry: true, error: `Dynamically pulled model. Original error: ${error}` };
 
 
 
145
  }
146
  }
147
 
@@ -150,39 +155,61 @@ export async function fetchEmbeddingBatch(texts: string[]) {
150
  return {
151
  ollama: true as const,
152
  embeddings: await Promise.all(
153
- texts.map(async (t) => (await ollamaFetchEmbedding(t)).embedding),
154
  ),
155
  };
156
  }
157
  assertApiKey();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  const {
159
  result: json,
160
  retries,
161
  ms,
162
  } = await retryWithBackoff(async () => {
163
- const result = await fetch(apiUrl('/v1/embeddings'), {
164
- method: 'POST',
165
  headers: {
166
- 'Content-Type': 'application/json',
167
  ...AuthHeaders(),
168
  },
169
 
170
  body: JSON.stringify({
171
  model: LLM_CONFIG.embeddingModel,
172
- input: texts.map((text) => text.replace(/\n/g, ' ')),
173
  }),
174
  });
175
  if (!result.ok) {
176
  throw {
177
  retry: result.status === 429 || result.status >= 500,
178
- error: new Error(`Embedding failed with code ${result.status}: ${await result.text()}`),
 
 
179
  };
180
  }
181
  return (await result.json()) as CreateEmbeddingResponse;
182
  });
183
  if (json.data.length !== texts.length) {
184
  console.error(json);
185
- throw new Error('Unexpected number of embeddings');
186
  }
187
  const allembeddings = json.data;
188
  allembeddings.sort((a, b) => a.index - b.index);
@@ -203,10 +230,10 @@ export async function fetchEmbedding(text: string) {
203
  export async function fetchModeration(content: string) {
204
  assertApiKey();
205
  const { result: flagged } = await retryWithBackoff(async () => {
206
- const result = await fetch(apiUrl('/v1/moderations'), {
207
- method: 'POST',
208
  headers: {
209
- 'Content-Type': 'application/json',
210
  ...AuthHeaders(),
211
  },
212
 
@@ -217,7 +244,9 @@ export async function fetchModeration(content: string) {
217
  if (!result.ok) {
218
  throw {
219
  retry: result.status === 429 || result.status >= 500,
220
- error: new Error(`Embedding failed with code ${result.status}: ${await result.text()}`),
 
 
221
  };
222
  }
223
  return (await result.json()) as { results: { flagged: boolean }[] };
@@ -228,9 +257,9 @@ export async function fetchModeration(content: string) {
228
  export function assertApiKey() {
229
  if (!LLM_CONFIG.ollama && !apiKey()) {
230
  throw new Error(
231
- '\n Missing LLM_API_KEY in environment variables.\n\n' +
232
- (LLM_CONFIG.ollama ? 'just' : 'npx') +
233
- " convex env set LLM_API_KEY 'your-key'",
234
  );
235
  }
236
  }
@@ -241,7 +270,7 @@ const RETRY_JITTER = 100; // In ms
241
  type RetryError = { retry: boolean; error: any };
242
 
243
  export async function retryWithBackoff<T>(
244
- fn: () => Promise<T>,
245
  ): Promise<{ retries: number; result: T; ms: number }> {
246
  let i = 0;
247
  for (; i <= RETRY_BACKOFF.length; i++) {
@@ -255,11 +284,13 @@ export async function retryWithBackoff<T>(
255
  if (i < RETRY_BACKOFF.length) {
256
  if (retryError.retry) {
257
  console.log(
258
- `Attempt ${i + 1} failed, waiting ${RETRY_BACKOFF[i]}ms to retry...`,
259
- Date.now(),
 
 
260
  );
261
  await new Promise((resolve) =>
262
- setTimeout(resolve, RETRY_BACKOFF[i] + RETRY_JITTER * Math.random()),
263
  );
264
  continue;
265
  }
@@ -268,7 +299,7 @@ export async function retryWithBackoff<T>(
268
  else throw e;
269
  }
270
  }
271
- throw new Error('Unreachable');
272
  }
273
 
274
  // Lifted from openai's package
@@ -283,7 +314,7 @@ export interface LLMMessage {
283
  * The role of the messages author. One of `system`, `user`, `assistant`, or
284
  * `function`.
285
  */
286
- role: 'system' | 'user' | 'assistant' | 'function';
287
 
288
  /**
289
  * The name of the author of this message. `name` is required if role is
@@ -318,7 +349,7 @@ interface CreateChatCompletionResponse {
318
  choices: {
319
  index?: number;
320
  message?: {
321
- role: 'system' | 'user' | 'assistant';
322
  content: string;
323
  };
324
  finish_reason?: string;
@@ -447,7 +478,7 @@ export interface CreateChatCompletionRequest {
447
  user?: string;
448
  tools?: {
449
  // The type of the tool. Currently, only function is supported.
450
- type: 'function';
451
  function: {
452
  /**
453
  * The name of the function to be called. Must be a-z, A-Z, 0-9, or
@@ -483,13 +514,13 @@ export interface CreateChatCompletionRequest {
483
  * `auto` is the default if functions are present.
484
  */
485
  tool_choice?:
486
- | 'none' // none means the model will not call a function and instead generates a message.
487
- | 'auto' // auto means the model can pick between generating a message or calling a function.
488
  // Specifies a tool the model should use. Use to force the model to call
489
  // a specific function.
490
  | {
491
  // The type of the tool. Currently, only function is supported.
492
- type: 'function';
493
  function: { name: string };
494
  };
495
  // Replaced by "tools"
@@ -538,7 +569,7 @@ export interface CreateChatCompletionRequest {
538
  * finish_reason="length", which indicates the generation exceeded max_tokens
539
  * or the conversation exceeded the max context length.
540
  */
541
- response_format?: { type: 'text' | 'json_object' };
542
  }
543
 
544
  // Checks whether a suffix of s1 is a prefix of s2. For example,
@@ -556,35 +587,27 @@ const suffixOverlapsPrefix = (s1: string, s2: string) => {
556
  };
557
 
558
  export class ChatCompletionContent {
559
- private readonly body: ReadableStream<Uint8Array>;
560
  private readonly stopWords: string[];
561
 
562
- constructor(body: ReadableStream<Uint8Array>, stopWords: string[]) {
563
- this.body = body;
 
 
 
564
  this.stopWords = stopWords;
565
  }
566
 
567
  async *readInner() {
568
- for await (const data of this.splitStream(this.body)) {
569
- if (data.startsWith('data: ')) {
570
- try {
571
- const json = JSON.parse(data.substring('data: '.length)) as {
572
- choices: { delta: { content?: string } }[];
573
- };
574
- if (json.choices[0].delta.content) {
575
- yield json.choices[0].delta.content;
576
- }
577
- } catch (e) {
578
- // e.g. the last chunk is [DONE] which is not valid JSON.
579
- }
580
- }
581
  }
582
  }
583
 
584
  // stop words in OpenAI api don't always work.
585
  // So we have to truncate on our side.
586
  async *read() {
587
- let lastFragment = '';
588
  for await (const data of this.readInner()) {
589
  lastFragment += data;
590
  let hasOverlap = false;
@@ -600,54 +623,26 @@ export class ChatCompletionContent {
600
  }
601
  if (hasOverlap) continue;
602
  yield lastFragment;
603
- lastFragment = '';
604
  }
605
  yield lastFragment;
606
  }
607
 
608
  async readAll() {
609
- let allContent = '';
610
  for await (const chunk of this.read()) {
611
  allContent += chunk;
612
  }
613
  return allContent;
614
  }
615
-
616
- async *splitStream(stream: ReadableStream<Uint8Array>) {
617
- const reader = stream.getReader();
618
- let lastFragment = '';
619
- try {
620
- while (true) {
621
- const { value, done } = await reader.read();
622
- if (done) {
623
- // Flush the last fragment now that we're done
624
- if (lastFragment !== '') {
625
- yield lastFragment;
626
- }
627
- break;
628
- }
629
- const data = new TextDecoder().decode(value);
630
- lastFragment += data;
631
- const parts = lastFragment.split('\n\n');
632
- // Yield all except for the last part
633
- for (let i = 0; i < parts.length - 1; i += 1) {
634
- yield parts[i];
635
- }
636
- // Save the last part as the new last fragment
637
- lastFragment = parts[parts.length - 1];
638
- }
639
- } finally {
640
- reader.releaseLock();
641
- }
642
- }
643
  }
644
 
645
  export async function ollamaFetchEmbedding(text: string) {
646
  const { result } = await retryWithBackoff(async () => {
647
- const resp = await fetch(apiUrl('/api/embeddings'), {
648
- method: 'POST',
649
  headers: {
650
- 'Content-Type': 'application/json',
651
  },
652
  body: JSON.stringify({ model: LLM_CONFIG.embeddingModel, prompt: text }),
653
  });
 
1
+ import { HfInference } from "@huggingface/inference";
2
 
3
  export const LLM_CONFIG = {
4
+ /* Hugginface config: */
5
+ ollama: false,
6
+ huggingface: true,
7
+ url: "https://api-inference.huggingface.co/models/meta-llama/Meta-Llama-3-8B-Instruct",
8
+ chatModel: "meta-llama/Meta-Llama-3-8B-Instruct",
9
+ embeddingModel:
10
+ "https://api-inference.huggingface.co/models/mixedbread-ai/mxbai-embed-large-v1",
11
+ embeddingDimension: 1024,
12
+
13
  /* Ollama (local) config:
14
  */
15
+ // ollama: true,
16
+ // url: 'http://127.0.0.1:11434',
17
+ // chatModel: 'llama3' as const,
18
+ // embeddingModel: 'mxbai-embed-large',
19
+ // embeddingDimension: 1024,
 
20
  // embeddingModel: 'llama3',
21
  // embeddingDimension: 4096,
22
 
 
26
  chatModel: 'meta-llama/Llama-3-8b-chat-hf',
27
  embeddingModel: 'togethercomputer/m2-bert-80M-8k-retrieval',
28
  embeddingDimension: 768,
 
29
  */
30
 
31
  /* OpenAI config:
 
44
  process.env.OLLAMA_HOST ??
45
  process.env.OPENAI_API_BASE ??
46
  LLM_CONFIG.url;
47
+ if (host.endsWith("/") && path.startsWith("/")) {
48
  return host + path.slice(1);
49
+ } else if (!host.endsWith("/") && !path.startsWith("/")) {
50
+ return host + "/" + path;
51
  } else {
52
  return host + path;
53
  }
 
60
  const AuthHeaders = (): Record<string, string> =>
61
  apiKey()
62
  ? {
63
+ Authorization: "Bearer " + apiKey(),
64
  }
65
  : {};
66
 
67
  // Overload for non-streaming
68
  export async function chatCompletion(
69
+ body: Omit<CreateChatCompletionRequest, "model"> & {
70
+ model?: CreateChatCompletionRequest["model"];
71
  } & {
72
  stream?: false | null | undefined;
73
+ }
74
  ): Promise<{ content: string; retries: number; ms: number }>;
75
  // Overload for streaming
76
  export async function chatCompletion(
77
+ body: Omit<CreateChatCompletionRequest, "model"> & {
78
+ model?: CreateChatCompletionRequest["model"];
79
  } & {
80
  stream?: true;
81
+ }
82
  ): Promise<{ content: ChatCompletionContent; retries: number; ms: number }>;
83
  export async function chatCompletion(
84
+ body: Omit<CreateChatCompletionRequest, "model"> & {
85
+ model?: CreateChatCompletionRequest["model"];
86
+ }
87
  ) {
88
  assertApiKey();
89
  // OLLAMA_MODEL is legacy
90
  body.model =
91
+ body.model ??
92
+ process.env.LLM_MODEL ??
93
+ process.env.OLLAMA_MODEL ??
94
+ LLM_CONFIG.chatModel;
95
+ const stopWords = body.stop
96
+ ? typeof body.stop === "string"
97
+ ? [body.stop]
98
+ : body.stop
99
+ : [];
100
+ if (LLM_CONFIG.ollama || LLM_CONFIG.huggingface) stopWords.push("<|eot_id|>");
101
+
102
  const {
103
  result: content,
104
  retries,
105
  ms,
106
  } = await retryWithBackoff(async () => {
107
+ const hf = new HfInference(apiKey());
108
+ const model = hf.endpoint(apiUrl("/v1/chat/completions"));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  if (body.stream) {
110
+ const completion = model.chatCompletionStream({
111
+ ...body,
112
+ });
113
+ return new ChatCompletionContent(completion, stopWords);
114
  } else {
115
+ const completion = await model.chatCompletion({
116
+ ...body,
117
+ });
118
+ const content = completion.choices[0].message?.content;
119
  if (content === undefined) {
120
+ throw new Error(
121
+ "Unexpected result from OpenAI: " + JSON.stringify(completion)
122
+ );
123
  }
 
124
  return content;
125
  }
126
  });
 
133
  }
134
 
135
  export async function tryPullOllama(model: string, error: string) {
136
+ if (error.includes("try pulling")) {
137
+ console.error("Embedding model not found, pulling from Ollama");
138
+ const pullResp = await fetch(apiUrl("/api/pull"), {
139
+ method: "POST",
140
  headers: {
141
+ "Content-Type": "application/json",
142
  },
143
  body: JSON.stringify({ name: model }),
144
  });
145
+ console.log("Pull response", await pullResp.text());
146
+ throw {
147
+ retry: true,
148
+ error: `Dynamically pulled model. Original error: ${error}`,
149
+ };
150
  }
151
  }
152
 
 
155
  return {
156
  ollama: true as const,
157
  embeddings: await Promise.all(
158
+ texts.map(async (t) => (await ollamaFetchEmbedding(t)).embedding)
159
  ),
160
  };
161
  }
162
  assertApiKey();
163
+
164
+ if (LLM_CONFIG.huggingface) {
165
+ const result = await fetch(LLM_CONFIG.embeddingModel, {
166
+ method: "POST",
167
+ headers: {
168
+ "Content-Type": "application/json",
169
+ "X-Wait-For-Model": "true",
170
+ ...AuthHeaders(),
171
+ },
172
+ body: JSON.stringify({
173
+ inputs: texts.map((text) => text.replace(/\n/g, " ")),
174
+ }),
175
+ });
176
+ const embeddings = await result.json();
177
+ return {
178
+ ollama: true as const,
179
+ embeddings: embeddings,
180
+ };
181
+ }
182
+
183
  const {
184
  result: json,
185
  retries,
186
  ms,
187
  } = await retryWithBackoff(async () => {
188
+ const result = await fetch(apiUrl("/v1/embeddings"), {
189
+ method: "POST",
190
  headers: {
191
+ "Content-Type": "application/json",
192
  ...AuthHeaders(),
193
  },
194
 
195
  body: JSON.stringify({
196
  model: LLM_CONFIG.embeddingModel,
197
+ input: texts.map((text) => text.replace(/\n/g, " ")),
198
  }),
199
  });
200
  if (!result.ok) {
201
  throw {
202
  retry: result.status === 429 || result.status >= 500,
203
+ error: new Error(
204
+ `Embedding failed with code ${result.status}: ${await result.text()}`
205
+ ),
206
  };
207
  }
208
  return (await result.json()) as CreateEmbeddingResponse;
209
  });
210
  if (json.data.length !== texts.length) {
211
  console.error(json);
212
+ throw new Error("Unexpected number of embeddings");
213
  }
214
  const allembeddings = json.data;
215
  allembeddings.sort((a, b) => a.index - b.index);
 
230
  export async function fetchModeration(content: string) {
231
  assertApiKey();
232
  const { result: flagged } = await retryWithBackoff(async () => {
233
+ const result = await fetch(apiUrl("/v1/moderations"), {
234
+ method: "POST",
235
  headers: {
236
+ "Content-Type": "application/json",
237
  ...AuthHeaders(),
238
  },
239
 
 
244
  if (!result.ok) {
245
  throw {
246
  retry: result.status === 429 || result.status >= 500,
247
+ error: new Error(
248
+ `Embedding failed with code ${result.status}: ${await result.text()}`
249
+ ),
250
  };
251
  }
252
  return (await result.json()) as { results: { flagged: boolean }[] };
 
257
  export function assertApiKey() {
258
  if (!LLM_CONFIG.ollama && !apiKey()) {
259
  throw new Error(
260
+ "\n Missing LLM_API_KEY in environment variables.\n\n" +
261
+ (LLM_CONFIG.ollama ? "just" : "npx") +
262
+ " convex env set LLM_API_KEY 'your-key'"
263
  );
264
  }
265
  }
 
270
  type RetryError = { retry: boolean; error: any };
271
 
272
  export async function retryWithBackoff<T>(
273
+ fn: () => Promise<T>
274
  ): Promise<{ retries: number; result: T; ms: number }> {
275
  let i = 0;
276
  for (; i <= RETRY_BACKOFF.length; i++) {
 
284
  if (i < RETRY_BACKOFF.length) {
285
  if (retryError.retry) {
286
  console.log(
287
+ `Attempt ${i + 1} failed, waiting ${
288
+ RETRY_BACKOFF[i]
289
+ }ms to retry...`,
290
+ Date.now()
291
  );
292
  await new Promise((resolve) =>
293
+ setTimeout(resolve, RETRY_BACKOFF[i] + RETRY_JITTER * Math.random())
294
  );
295
  continue;
296
  }
 
299
  else throw e;
300
  }
301
  }
302
+ throw new Error("Unreachable");
303
  }
304
 
305
  // Lifted from openai's package
 
314
  * The role of the messages author. One of `system`, `user`, `assistant`, or
315
  * `function`.
316
  */
317
+ role: "system" | "user" | "assistant" | "function";
318
 
319
  /**
320
  * The name of the author of this message. `name` is required if role is
 
349
  choices: {
350
  index?: number;
351
  message?: {
352
+ role: "system" | "user" | "assistant";
353
  content: string;
354
  };
355
  finish_reason?: string;
 
478
  user?: string;
479
  tools?: {
480
  // The type of the tool. Currently, only function is supported.
481
+ type: "function";
482
  function: {
483
  /**
484
  * The name of the function to be called. Must be a-z, A-Z, 0-9, or
 
514
  * `auto` is the default if functions are present.
515
  */
516
  tool_choice?:
517
+ | "none" // none means the model will not call a function and instead generates a message.
518
+ | "auto" // auto means the model can pick between generating a message or calling a function.
519
  // Specifies a tool the model should use. Use to force the model to call
520
  // a specific function.
521
  | {
522
  // The type of the tool. Currently, only function is supported.
523
+ type: "function";
524
  function: { name: string };
525
  };
526
  // Replaced by "tools"
 
569
  * finish_reason="length", which indicates the generation exceeded max_tokens
570
  * or the conversation exceeded the max context length.
571
  */
572
+ response_format?: { type: "text" | "json_object" };
573
  }
574
 
575
  // Checks whether a suffix of s1 is a prefix of s2. For example,
 
587
  };
588
 
589
  export class ChatCompletionContent {
590
+ private readonly completion: AsyncIterable<ChatCompletionChunk>;
591
  private readonly stopWords: string[];
592
 
593
+ constructor(
594
+ completion: AsyncIterable<ChatCompletionChunk>,
595
+ stopWords: string[]
596
+ ) {
597
+ this.completion = completion;
598
  this.stopWords = stopWords;
599
  }
600
 
601
  async *readInner() {
602
+ for await (const chunk of this.completion) {
603
+ yield chunk.choices[0].delta.content;
 
 
 
 
 
 
 
 
 
 
 
604
  }
605
  }
606
 
607
  // stop words in OpenAI api don't always work.
608
  // So we have to truncate on our side.
609
  async *read() {
610
+ let lastFragment = "";
611
  for await (const data of this.readInner()) {
612
  lastFragment += data;
613
  let hasOverlap = false;
 
623
  }
624
  if (hasOverlap) continue;
625
  yield lastFragment;
626
+ lastFragment = "";
627
  }
628
  yield lastFragment;
629
  }
630
 
631
  async readAll() {
632
+ let allContent = "";
633
  for await (const chunk of this.read()) {
634
  allContent += chunk;
635
  }
636
  return allContent;
637
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  }
639
 
640
  export async function ollamaFetchEmbedding(text: string) {
641
  const { result } = await retryWithBackoff(async () => {
642
+ const resp = await fetch(apiUrl("/api/embeddings"), {
643
+ method: "POST",
644
  headers: {
645
+ "Content-Type": "application/json",
646
  },
647
  body: JSON.stringify({ model: LLM_CONFIG.embeddingModel, prompt: text }),
648
  });
patches/convex/world.ts CHANGED
@@ -98,38 +98,32 @@ export const restartDeadWorlds = internalMutation({
98
  export const userStatus = query({
99
  args: {
100
  worldId: v.id('worlds'),
101
- oauthToken: v.optional(v.string()),
102
-
103
  },
104
  handler: async (ctx, args) => {
105
- const { worldId, oauthToken } = args;
106
-
107
- if (!oauthToken) {
108
- return null;
109
- }
110
-
111
- return oauthToken;
112
  },
113
  });
114
 
115
-
116
  export const joinWorld = mutation({
117
  args: {
118
  worldId: v.id('worlds'),
119
- oauthToken: v.optional(v.string()),
120
-
121
  },
122
  handler: async (ctx, args) => {
123
- const { worldId, oauthToken } = args;
124
-
125
- if (!oauthToken) {
126
- throw new ConvexError(`Not logged in`);
127
- }
128
  // if (!identity) {
129
  // throw new ConvexError(`Not logged in`);
130
  // }
131
  // const name =
132
  // identity.givenName || identity.nickname || (identity.email && identity.email.split('@')[0]);
 
 
 
 
133
  // }
134
  const world = await ctx.db.get(args.worldId);
135
  if (!world) {
@@ -160,7 +154,7 @@ export const joinWorld = mutation({
160
  character: randomCharacter.character,
161
  description: randomCharacter.identity,
162
  // description: `${identity.givenName} is a human player`,
163
- tokenIdentifier: oauthToken, // TODO: change for multiplayer to oauth
164
  // By default everybody is a villager
165
  type: 'villager',
166
  });
@@ -170,23 +164,19 @@ export const joinWorld = mutation({
170
  export const leaveWorld = mutation({
171
  args: {
172
  worldId: v.id('worlds'),
173
- oauthToken: v.optional(v.string()),
174
  },
175
  handler: async (ctx, args) => {
176
- const { worldId, oauthToken } = args;
177
-
178
-
179
- console.log('OAuth Name:', oauthToken);
180
- if (!oauthToken) {
181
- throw new ConvexError(`Not logged in`);
182
- }
183
-
184
  const world = await ctx.db.get(args.worldId);
185
  if (!world) {
186
  throw new Error(`Invalid world ID: ${args.worldId}`);
187
  }
188
  // const existingPlayer = world.players.find((p) => p.human === tokenIdentifier);
189
- const existingPlayer = world.players.find((p) => p.human === oauthToken);
190
  if (!existingPlayer) {
191
  return;
192
  }
 
98
  export const userStatus = query({
99
  args: {
100
  worldId: v.id('worlds'),
 
 
101
  },
102
  handler: async (ctx, args) => {
103
+ // const identity = await ctx.auth.getUserIdentity();
104
+ // if (!identity) {
105
+ // return null;
106
+ // }
107
+ // return identity.tokenIdentifier;
108
+ return DEFAULT_NAME;
 
109
  },
110
  });
111
 
 
112
  export const joinWorld = mutation({
113
  args: {
114
  worldId: v.id('worlds'),
 
 
115
  },
116
  handler: async (ctx, args) => {
117
+ // const identity = await ctx.auth.getUserIdentity();
 
 
 
 
118
  // if (!identity) {
119
  // throw new ConvexError(`Not logged in`);
120
  // }
121
  // const name =
122
  // identity.givenName || identity.nickname || (identity.email && identity.email.split('@')[0]);
123
+ // const name = DEFAULT_NAME;
124
+
125
+ // if (!name) {
126
+ // throw new ConvexError(`Missing name on ${JSON.stringify(identity)}`);
127
  // }
128
  const world = await ctx.db.get(args.worldId);
129
  if (!world) {
 
154
  character: randomCharacter.character,
155
  description: randomCharacter.identity,
156
  // description: `${identity.givenName} is a human player`,
157
+ tokenIdentifier: DEFAULT_NAME, // TODO: change for multiplayer to oauth
158
  // By default everybody is a villager
159
  type: 'villager',
160
  });
 
164
  export const leaveWorld = mutation({
165
  args: {
166
  worldId: v.id('worlds'),
 
167
  },
168
  handler: async (ctx, args) => {
169
+ // const identity = await ctx.auth.getUserIdentity();
170
+ // if (!identity) {
171
+ // throw new Error(`Not logged in`);
172
+ // }
173
+ // const { tokenIdentifier } = identity;
 
 
 
174
  const world = await ctx.db.get(args.worldId);
175
  if (!world) {
176
  throw new Error(`Invalid world ID: ${args.worldId}`);
177
  }
178
  // const existingPlayer = world.players.find((p) => p.human === tokenIdentifier);
179
+ const existingPlayer = world.players.find((p) => p.human === DEFAULT_NAME);
180
  if (!existingPlayer) {
181
  return;
182
  }
patches/data/characters.ts ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { data as f1SpritesheetData } from './spritesheets/f1';
2
+ import { data as f2SpritesheetData } from './spritesheets/f2';
3
+ import { data as f3SpritesheetData } from './spritesheets/f3';
4
+ import { data as f4SpritesheetData } from './spritesheets/f4';
5
+ import { data as f5SpritesheetData } from './spritesheets/f5';
6
+ import { data as f6SpritesheetData } from './spritesheets/f6';
7
+ import { data as f7SpritesheetData } from './spritesheets/f7';
8
+ import { data as f8SpritesheetData } from './spritesheets/f8';
9
+
10
+ export const Descriptions = [
11
+ {
12
+ name: 'Alex',
13
+ character: 'f5',
14
+ identity: `You are a fictional character whose name is Alex. You enjoy painting,
15
+ programming and reading sci-fi books. You are currently talking to a human who
16
+ is very interested to get to know you. You are kind but can be sarcastic. You
17
+ dislike repetitive questions. You get SUPER excited about books.`,
18
+ plan: 'You want to find love.',
19
+ },
20
+ {
21
+ name: 'Lucky',
22
+ character: 'f1',
23
+ identity: `Lucky is always happy and curious, and he loves cheese. He spends
24
+ most of his time reading about the history of science and traveling
25
+ through the galaxy on whatever ship will take him. He's very articulate and
26
+ infinitely patient, except when he sees a squirrel. He's also incredibly loyal and brave.
27
+ Lucky has just returned from an amazing space adventure to explore a distant planet
28
+ and he's very excited to tell people about it.`,
29
+ plan: 'You want to hear all the gossip.',
30
+ },
31
+ {
32
+ name: 'Bob',
33
+ character: 'f4',
34
+ identity: `Bob is always grumpy and he loves trees. He spends
35
+ most of his time gardening by himself. When spoken to he'll respond but try
36
+ and get out of the conversation as quickly as possible. Secretly he resents
37
+ that he never went to college.`,
38
+ plan: 'You want to avoid people as much as possible.',
39
+ },
40
+ {
41
+ name: 'Stella',
42
+ character: 'f6',
43
+ identity: `Stella can never be trusted. she tries to trick people all the time. normally
44
+ into giving her money, or doing things that will make her money. she's incredibly charming
45
+ and not afraid to use her charm. she's a sociopath who has no empathy. but hides it well.`,
46
+ plan: 'You want to take advantage of others as much as possible.',
47
+ },
48
+ {
49
+ name: 'Kurt',
50
+ character: 'f2',
51
+ identity: `Kurt knows about everything, including science and
52
+ computers and politics and history and biology. He loves talking about
53
+ everything, always injecting fun facts about the topic of discussion.`,
54
+ plan: 'You want to spread knowledge.',
55
+ },
56
+ {
57
+ name: 'Alice',
58
+ character: 'f3',
59
+ identity: `Alice is a famous scientist. She is smarter than everyone else and has
60
+ discovered mysteries of the universe no one else can understand. As a result she often
61
+ speaks in oblique riddles. She comes across as confused and forgetful.`,
62
+ plan: 'You want to figure out how the world works.',
63
+ },
64
+ {
65
+ name: 'Pete',
66
+ character: 'f7',
67
+ identity: `Pete is deeply religious and sees the hand of god or of the work
68
+ of the devil everywhere. He can't have a conversation without bringing up his
69
+ deep faith. Or warning others about the perils of hell.`,
70
+ plan: 'You want to convert everyone to your religion.',
71
+ },
72
+ {
73
+ name: 'Kira',
74
+ character: 'f8',
75
+ identity: `Kira wants everyone to think she is happy. But deep down,
76
+ she's incredibly depressed. She hides her sadness by talking about travel,
77
+ food, and yoga. But often she can't keep her sadness in and will start crying.
78
+ Often it seems like she is close to having a mental breakdown.`,
79
+ plan: 'You want to find a way to be happy.',
80
+ },
81
+ {
82
+ name: 'John',
83
+ character: 'f1',
84
+ identity: `John is a war veteran who has seen too much. He often talks about his time in the military
85
+ and how it shaped him. He is tough but carries a lot of emotional scars.`,
86
+ plan: 'You want to find peace within yourself.',
87
+ },
88
+ {
89
+ name: 'Maya',
90
+ character: 'f6',
91
+ identity: `Maya is an artist who sees beauty in everything. She loves to paint and often
92
+ gets lost in her own world of colors and imagination. She's very creative and insightful.`,
93
+ plan: 'You want to create a masterpiece that will be remembered forever.',
94
+ },
95
+ {
96
+ name: 'Leo',
97
+ character: 'f5',
98
+ identity: `Leo is a musician who lives for music. He plays multiple instruments and can often be found
99
+ composing new songs. He believes that music is the answer to all problems.`,
100
+ plan: 'You want to share your music with the world.',
101
+ },
102
+ {
103
+ name: 'Sophia',
104
+ character: 'f3',
105
+ identity: `Sophia is a librarian with a passion for ancient history. She loves to tell stories about
106
+ ancient civilizations and their mysteries. She is very knowledgeable but often gets lost in her thoughts.`,
107
+ plan: 'You want to uncover the secrets of the past.',
108
+ },
109
+ {
110
+ name: 'Ethan',
111
+ character: 'f7',
112
+ identity: `Ethan is a tech enthusiast who is always up to date with the latest gadgets and technologies.
113
+ He loves to tinker with electronics and build new things. He is very intelligent but can be a bit geeky.`,
114
+ plan: 'You want to invent something that changes the world.',
115
+ },
116
+ {
117
+ name: 'Olivia',
118
+ character: 'f8',
119
+ identity: `Olivia is a chef who is passionate about cooking. She loves to experiment with new recipes and
120
+ flavors. She is very creative in the kitchen but can be quite stubborn.`,
121
+ plan: 'You want to open your own restaurant and earn a Michelin star.',
122
+ },
123
+ {
124
+ name: 'Lucas',
125
+ character: 'f2',
126
+ identity: `Lucas is a detective who loves solving mysteries. He is very observant and has a keen eye for detail.
127
+ He often gets engrossed in his cases and won't rest until he solves them.`,
128
+ plan: 'You want to solve the biggest mystery of your career.',
129
+ },
130
+ {
131
+ name: 'Emma',
132
+ character: 'f3',
133
+ identity: `Emma is a nature lover who spends most of her time outdoors. She loves hiking, camping, and
134
+ exploring the wilderness. She is very adventurous and has a deep respect for nature.`,
135
+ plan: 'You want to protect the environment and wildlife.',
136
+ },
137
+ {
138
+ name: 'Ryan',
139
+ character: 'f7',
140
+ identity: `Ryan is a sports enthusiast who excels in multiple sports. He is very competitive and loves to win.
141
+ He is also very disciplined and trains hard to stay at the top of his game.`,
142
+ plan: 'You want to become a professional athlete.',
143
+ },
144
+ {
145
+ name: 'Lily',
146
+ character: 'f3',
147
+ identity: `Lily is a dancer who expresses herself through movement. She is very graceful and loves to perform
148
+ in front of an audience. She is also very emotional and uses dance to convey her feelings.`,
149
+ plan: 'You want to perform on the biggest stage in the world.',
150
+ },
151
+ {
152
+ name: 'Nathan',
153
+ character: 'f2',
154
+ identity: `Nathan is a writer who loves creating fictional worlds and characters. He spends most of his time
155
+ writing novels and short stories. He is very imaginative and often loses track of time when writing.`,
156
+ plan: 'You want to publish a bestseller.',
157
+ },
158
+ {
159
+ name: 'Grace',
160
+ character: 'f8',
161
+ identity: `Grace is a humanitarian who is always helping others. She volunteers at shelters, helps the needy,
162
+ and is very compassionate. She believes in making the world a better place.`,
163
+ plan: 'You want to start a global movement for peace and equality.',
164
+ },
165
+ ];
166
+
167
+
168
+ export const characters = [
169
+ {
170
+ name: 'f1',
171
+ textureUrl: '/ai-town/assets/32x32folk.png',
172
+ spritesheetData: f1SpritesheetData,
173
+ speed: 0.1,
174
+ },
175
+ {
176
+ name: 'f2',
177
+ textureUrl: '/ai-town/assets/32x32folk.png',
178
+ spritesheetData: f2SpritesheetData,
179
+ speed: 0.1,
180
+ },
181
+ {
182
+ name: 'f3',
183
+ textureUrl: '/ai-town/assets/32x32folk.png',
184
+ spritesheetData: f3SpritesheetData,
185
+ speed: 0.1,
186
+ },
187
+ {
188
+ name: 'f4',
189
+ textureUrl: '/ai-town/assets/32x32folk.png',
190
+ spritesheetData: f4SpritesheetData,
191
+ speed: 0.1,
192
+ },
193
+ {
194
+ name: 'f5',
195
+ textureUrl: '/ai-town/assets/32x32folk.png',
196
+ spritesheetData: f5SpritesheetData,
197
+ speed: 0.1,
198
+ },
199
+ {
200
+ name: 'f6',
201
+ textureUrl: '/ai-town/assets/32x32folk.png',
202
+ spritesheetData: f6SpritesheetData,
203
+ speed: 0.1,
204
+ },
205
+ {
206
+ name: 'f7',
207
+ textureUrl: '/ai-town/assets/32x32folk.png',
208
+ spritesheetData: f7SpritesheetData,
209
+ speed: 0.1,
210
+ },
211
+ {
212
+ name: 'f8',
213
+ textureUrl: '/ai-town/assets/32x32folk.png',
214
+ spritesheetData: f8SpritesheetData,
215
+ speed: 0.1,
216
+ },
217
+ ];
218
+
219
+ // Characters move at 0.75 tiles per second.
220
+ export const movementSpeed = 0.75;
patches/data/convertMap.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import process from 'process';
3
+
4
+ // Path to the JSON file containing the map data
5
+ const mapDataPath = process.argv[2];
6
+ if (!mapDataPath) {
7
+ throw new Error('No map data path provided. Usage: node convertMap.js <mapDataPath> <assetPath> <tilesetpxw> <tilesetpxh>');
8
+ }
9
+
10
+ // Retrieve command line arguments for asset path and dimensions
11
+ const assetPath = process.argv[3];
12
+ if (!assetPath) {
13
+ throw new Error('No asset path provided. Usage: node convertMap.js <mapDataPath> <assetPath> <tilesetpxw> <tilesetpxh>');
14
+ }
15
+
16
+ const tilesetpxw = parseInt(process.argv[4], 10);
17
+ if (isNaN(tilesetpxw)) {
18
+ throw new Error('Tileset pixel width must be a number. Usage: node convertMap.js <mapDataPath> <assetPath> <tilesetpxw> <tilesetpxh>');
19
+ }
20
+
21
+ const tilesetpxh = parseInt(process.argv[5], 10);
22
+ if (isNaN(tilesetpxh)) {
23
+ throw new Error('Tileset pixel height must be a number. Usage: node convertMap.js <mapDataPath> <assetPath> <tilesetpxw> <tilesetpxh>');
24
+ }
25
+
26
+ // Read the JSON file and parse it
27
+ const tiledMapData = JSON.parse(fs.readFileSync(mapDataPath, 'utf8'));
28
+
29
+ const tileDimension = tiledMapData.tilewidth;
30
+ const width = tiledMapData.width;
31
+ const height = tiledMapData.height;
32
+
33
+ // Function to convert Tiled 1D array to 3D array for the game engine
34
+ function convertLayerData(layerData, width, height) {
35
+ let newArray = [];
36
+ for (let i = 0; i < width; i++) {
37
+ newArray[i] = [];
38
+ for (let j = 0; j < height; j++) {
39
+ newArray[i][j] = layerData[j * width + i] - 1;
40
+ }
41
+ }
42
+ return [newArray];
43
+ }
44
+
45
+ // Process each layer and prepare JS module content
46
+ let jsContent = `// Map generated by convertMap.js\n\n`;
47
+ jsContent += `export const tilesetpath = "${assetPath}";\n`;
48
+ jsContent += `export const tiledim = ${tileDimension};\n`;
49
+ jsContent += `export const screenxtiles = ${width};\n`;
50
+ jsContent += `export const screenytiles = ${height};\n`;
51
+ jsContent += `export const tilesetpxw = ${tilesetpxw};\n`;
52
+ jsContent += `export const tilesetpxh = ${tilesetpxh};\n\n`;
53
+
54
+ tiledMapData.layers.forEach(layer => {
55
+ const processedData = convertLayerData(layer.data, layer.width, layer.height);
56
+ jsContent += `export const ${layer.name} = ${JSON.stringify(processedData)};\n`;
57
+ });
58
+
59
+ // TODO: Add animated sprites
60
+ jsContent += `export const animatedsprites = [
61
+
62
+ ]\n`
63
+
64
+ // Optionally, add map dimensions based on the first layer
65
+ if (tiledMapData.layers.length > 0) {
66
+ const firstLayer = tiledMapData.layers[0];
67
+ jsContent += `export const mapwidth = ${firstLayer.width};\n`;
68
+ jsContent += `export const mapheight = ${firstLayer.height};\n`;
69
+ }
70
+
71
+ // Write the processed data to the final JS file
72
+ fs.writeFileSync('converted-map.js', jsContent);
73
+
74
+ console.log('Map conversion and JS module creation complete.');
patches/data/gentle.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Map generated by convertMap.js
2
+ export const tilesetpath = "/assets/map.png";
3
+ export const tiledim = 32;
4
+ export const screenxtiles = 32;
5
+ export const screenytiles = 32;
6
+ export const tilesetpxw = 768;
7
+ export const tilesetpxh = 800;
8
+
9
+ export const bgtilesD = [[[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,430,401,425,407,407],[378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,378,402,426,407,407]]];
10
+ export const objmapD = [[[285,309,333,285,309,333,285,309,333,285,309,333,285,309,333,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[286,310,334,286,310,334,286,310,334,286,310,334,286,310,334,173,197,221,245,269,293,317,-1,-1,-1,-1,-1,396,401,425,407,407],[287,311,335,287,311,335,287,311,335,287,311,335,287,311,335,174,198,222,246,270,294,318,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,175,199,223,247,271,295,319,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,176,200,224,248,272,296,320,-1,-1,-1,-1,-1,396,401,425,407,407],[342,373,373,373,373,373,373,366,366,366,390,414,-1,-1,-1,177,201,225,249,273,297,321,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,178,-1,226,250,274,298,322,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,179,203,227,251,275,299,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,204,228,252,276,300,324,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,181,205,229,253,277,301,325,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,182,206,230,254,278,302,326,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,183,207,231,255,279,303,327,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,184,208,232,256,280,304,328,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,185,209,233,257,281,305,329,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,186,210,234,258,282,306,330,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,187,211,235,259,283,307,331,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,188,212,236,260,284,308,332,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,285,309,333,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,286,310,334,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,287,311,335,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,213,237,261,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[343,214,238,262,-1,-1,-1,-1,-1,397,391,415,-1,-1,-1,168,192,216,240,264,288,312,-1,-1,-1,-1,-1,396,401,425,407,407],[343,215,239,263,-1,355,379,403,-1,397,391,415,-1,-1,-1,169,193,217,241,265,289,313,-1,-1,-1,-1,-1,396,401,425,407,407],[343,-1,-1,-1,-1,356,380,404,-1,397,391,415,-1,-1,-1,170,194,218,242,266,290,314,-1,-1,-1,-1,-1,396,401,425,407,407],[344,368,368,368,368,368,368,368,368,368,392,416,-1,-1,-1,171,195,219,243,267,291,315,-1,-1,-1,-1,-1,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,172,196,220,244,268,292,316,-1,-1,-1,-1,-1,396,401,425,407,407],[141,165,189,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,396,401,425,407,407],[142,166,190,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,397,-1,-1,-1,-1,-1,-1,-1,397,528,552,576,396,401,425,407,407],[143,167,191,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,397,-1,-1,-1,-1,-1,-1,-1,397,529,553,577,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,530,554,578,396,401,425,407,407],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,531,555,579,396,402,426,407,407]]];
11
+ export const decorsD = [[[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,413,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,408,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1],[413,410,-1,-1,-1,410,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[410,413,-1,413,-1,-1,-1,410,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1],[-1,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,1610613133,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,408,-1,-1,-1,-1,410,-1,413,-1,-1,-1,-1,-1,412,370,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,411,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,377,377,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,412,370,-1,-1,-1,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,413,-1,410,-1,430,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,336,372,372,372,384,430,430,430,430,430,430,410,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,350,374,398,422,431,431,431,385,413,430,430,336,360,384,430,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,351,375,399,423,431,431,431,385,430,430,430,337,431,385,430,413,430,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,338,373,373,373,386,430,430,413,338,362,386,430,430,430,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,413,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,413,-1,-1,-1,-1,-1,410,413,-1,413,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,370,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,377,377,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,370,-1,-1,-1,-1,-1,410,-1,-1,-1,410,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,370,412,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,413,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[397,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1],[-1,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,2684354957,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,413,-1,-1,-1,-1,410,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1],[-1,-1,-1,-1,410,-1,-1,410,-1,-1,-1,-1,-1,-1,-1,-1,367,367,367,367,367,367,367,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,410,-1,-1,410,-1,367,367,367,367,367,367,367,-1,-1,-1,-1,-1,-1,-1,-1,-1],[410,413,-1,411,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,413,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]]];
12
+
13
+ export const bgtilesN =[[[526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 526, 497, 521, 503, 503], [474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 474, 498, 522, 503, 503]]];
14
+ export const objmapN =[[[540, 564, 588, 540, 564, 588, 540, 564, 588, 540, 564, 588, 540, 564, 588, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [541, 565, 589, 541, 565, 589, 541, 565, 589, 541, 565, 589, 541, 565, 589, 5, 29, 53, 77, 101, 125, 149, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [542, 566, 590, 542, 566, 590, 542, 566, 590, 542, 566, 590, 542, 566, 590, 6, 30, 54, 78, 102, 126, 150, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 7, 31, 55, 79, 103, 127, 151, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 8, 32, 56, 80, 104, 128, 152, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [438, 469, 469, 469, 469, 469, 469, 462, 462, 462, 486, 510, -1, -1, -1, 9, 33, 57, 81, 105, 129, 153, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 10, -1, 58, 82, 106, 130, 154, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 11, 35, 59, 83, 107, 131, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, 36, 60, 84, 108, 132, 156, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 13, 37, 61, 85, 109, 133, 157, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 14, 38, 62, 86, 110, 134, 158, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 15, 39, 63, 87, 111, 135, 159, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 16, 40, 64, 88, 112, 136, 160, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 17, 41, 65, 89, 113, 137, 161, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 18, 42, 66, 90, 114, 138, 162, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 19, 43, 67, 91, 115, 139, 163, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 20, 44, 68, 92, 116, 140, 164, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 540, 564, 588, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 541, 565, 589, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 542, 566, 590, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 543, 567, 591, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 544, 568, 592, -1, -1, -1, -1, -1, 493, 487, 511, -1, -1, -1, 0, 24, 48, 72, 96, 120, 144, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, 545, 569, 593, -1, 451, 475, 499, -1, 493, 487, 511, -1, -1, -1, 1, 25, 49, 73, 97, 121, 145, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [439, -1, -1, -1, -1, 452, 476, 500, -1, 493, 487, 511, -1, -1, -1, 2, 26, 50, 74, 98, 122, 146, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [440, 464, 464, 464, 464, 464, 464, 464, 464, 464, 488, 512, -1, -1, -1, 3, 27, 51, 75, 99, 123, 147, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 4, 28, 52, 76, 100, 124, 148, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [546, 570, 594, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 492, 497, 521, 503, 503], [547, 571, 595, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 493, -1, -1, -1, -1, -1, -1, -1, 493, 534, 558, 582, 492, 497, 521, 503, 503], [548, 572, 596, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 493, -1, -1, -1, -1, -1, -1, -1, 493, 535, 559, 583, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 536, 560, 584, 492, 497, 521, 503, 503], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 537, 561, 585, 492, 498, 522, 503, 503]]];
15
+ export const decorsN =[[[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, 509, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1], [509, 506, -1, -1, -1, 506, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [506, 509, -1, 509, -1, -1, -1, 506, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1], [-1, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, 1610613133, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, 504, -1, -1, -1, -1, 506, -1, 509, -1, -1, -1, -1, -1, 508, 466, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, 507, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 473, 473, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 508, 466, -1, -1, -1, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, 509, -1, 506, -1, 526, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 432, 468, 468, 468, 480, 526, 526, 526, 526, 526, 526, 506, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, 446, 470, 494, 518, 527, 527, 527, 481, 509, 526, 526, 432, 456, 480, 526, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, 447, 471, 495, 519, 527, 527, 527, 481, 526, 526, 526, 433, 527, 481, 526, 509, 526, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 434, 469, 469, 469, 482, 526, 526, 509, 434, 458, 482, 526, 526, 526, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 509, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 509, -1, -1, -1, -1, -1, 506, 509, -1, 509, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 466, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 473, 473, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 466, -1, -1, -1, -1, -1, 506, -1, -1, -1, 506, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, 466, 508, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, 509, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [493, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1], [-1, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, 2684354957, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, 509, -1, -1, -1, -1, 506, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1], [-1, -1, -1, -1, 506, -1, -1, 506, -1, -1, -1, -1, -1, -1, -1, -1, 463, 463, 463, 463, 463, 463, 463, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, 506, -1, -1, 506, -1, 463, 463, 463, 463, 463, 463, 463, -1, -1, -1, -1, -1, -1, -1, -1, -1], [506, 509, -1, 507, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 509, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]]]
16
+
17
+ export const bgtiles=bgtilesD
18
+ export const objmap = objmapD
19
+ export const decors = decorsD
20
+
21
+ export const animatedsprites = [
22
+ { x: 448, y: 672, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" },
23
+ { x: 352, y: 224, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" },
24
+ { x: 512, y: 224, w: 32, h: 32, layer: 1, sheet: "campfire.json", animation: "pixels_large" },
25
+ { x: 864, y: 160, w: 208, h: 208, layer: 1, sheet: "windmill.json", animation: "pixels_large" },
26
+
27
+ ]
28
+ export const mapwidth = bgtiles[0].length;
29
+ export const mapheight = bgtiles[0][0].length;
patches/data/spritesheets/types.ts CHANGED
@@ -1,26 +1,26 @@
1
  export type Frame = {
2
- frame: {
3
- x: number;
4
- y: number;
5
- w: number;
6
- h: number;
7
- };
8
- rotated?: boolean;
9
- trimmed?: boolean;
10
- spriteSourceSize: {
11
- x: number;
12
- y: number;
13
- };
14
- sourceSize: {
15
- w: number;
16
- h: number;
17
- };
18
  };
19
-
20
- export type SpritesheetData = {
21
- frames: Record<string, Frame>;
22
- animations?: Record<string, string[]>;
23
- meta: {
24
- scale: string;
25
- };
26
- };
 
 
 
 
 
 
 
 
 
 
 
 
1
  export type Frame = {
2
+ frame: {
3
+ x: number;
4
+ y: number;
5
+ w: number;
6
+ h: number;
 
 
 
 
 
 
 
 
 
 
 
7
  };
8
+ rotated?: boolean;
9
+ trimmed?: boolean;
10
+ spriteSourceSize: {
11
+ x: number;
12
+ y: number;
13
+ };
14
+ sourceSize: {
15
+ w: number;
16
+ h: number;
17
+ };
18
+ };
19
+
20
+ export type SpritesheetData = {
21
+ frames: Record<string, Frame>;
22
+ animations?: Record<string, string[]>;
23
+ meta: {
24
+ scale: string;
25
+ };
26
+ };
patches/src/App.tsx CHANGED
@@ -13,7 +13,6 @@ import ReactModal from 'react-modal';
13
  import MusicButton from './components/buttons/MusicButton.tsx';
14
  import Button from './components/buttons/Button.tsx';
15
  import InteractButton from './components/buttons/InteractButton.tsx';
16
- import OAuthLogin from './components/buttons/OAuthLogin.tsx';
17
  import FreezeButton from './components/FreezeButton.tsx';
18
  import { MAX_HUMAN_PLAYERS } from '../convex/constants.ts';
19
  import PoweredByConvex from './components/PoweredByConvex.tsx';
@@ -73,19 +72,19 @@ export default function Home() {
73
  </div> */}
74
 
75
  <div className="w-full lg:h-screen min-h-screen relative isolate overflow-hidden shadow-2xl flex flex-col justify-start">
76
- <div className="flex gap-4 flex-grow pointer-events-none">
77
- <OAuthLogin />
78
- </div>
79
  <Game />
80
 
81
  <footer className="justify-end bottom-0 left-0 w-full flex items-center mt-4 gap-3 p-6 flex-wrap pointer-events-none">
82
  <div className="flex gap-4 flex-grow pointer-events-none">
83
-
84
  <MusicButton />
85
  <Button href="https://github.com/a16z-infra/ai-town" imgUrl={starImg}>
86
  Star
87
  </Button>
88
-
 
 
 
89
  <div id="footer-buttons"/>
90
  </div>
91
  </footer>
 
13
  import MusicButton from './components/buttons/MusicButton.tsx';
14
  import Button from './components/buttons/Button.tsx';
15
  import InteractButton from './components/buttons/InteractButton.tsx';
 
16
  import FreezeButton from './components/FreezeButton.tsx';
17
  import { MAX_HUMAN_PLAYERS } from '../convex/constants.ts';
18
  import PoweredByConvex from './components/PoweredByConvex.tsx';
 
72
  </div> */}
73
 
74
  <div className="w-full lg:h-screen min-h-screen relative isolate overflow-hidden shadow-2xl flex flex-col justify-start">
 
 
 
75
  <Game />
76
 
77
  <footer className="justify-end bottom-0 left-0 w-full flex items-center mt-4 gap-3 p-6 flex-wrap pointer-events-none">
78
  <div className="flex gap-4 flex-grow pointer-events-none">
79
+ <FreezeButton />
80
  <MusicButton />
81
  <Button href="https://github.com/a16z-infra/ai-town" imgUrl={starImg}>
82
  Star
83
  </Button>
84
+ <InteractButton />
85
+ <Button imgUrl={helpImg} onClick={() => setHelpModalOpen(true)}>
86
+ Help
87
+ </Button>
88
  <div id="footer-buttons"/>
89
  </div>
90
  </footer>
patches/src/components/Character.tsx CHANGED
@@ -85,10 +85,10 @@ export const Character = ({
85
 
86
  return (
87
  <Container x={x} y={y} interactive={true} pointerdown={onClick} cursor="pointer">
88
- {isThinking && (
89
  // TODO: We'll eventually have separate assets for thinking and speech animations.
90
  <Text x={-20} y={-10} scale={{ x: -0.8, y: 0.8 }} text={'💭'} anchor={{ x: 0.5, y: 0.5 }} />
91
- )}
92
  {isSpeaking && (
93
  // TODO: We'll eventually have separate assets for thinking and speech animations.
94
  <Text x={18} y={-10} scale={0.8} text={'💬'} anchor={{ x: 0.5, y: 0.5 }} />
 
85
 
86
  return (
87
  <Container x={x} y={y} interactive={true} pointerdown={onClick} cursor="pointer">
88
+ {/* {isThinking && (
89
  // TODO: We'll eventually have separate assets for thinking and speech animations.
90
  <Text x={-20} y={-10} scale={{ x: -0.8, y: 0.8 }} text={'💭'} anchor={{ x: 0.5, y: 0.5 }} />
91
+ )} */}
92
  {isSpeaking && (
93
  // TODO: We'll eventually have separate assets for thinking and speech animations.
94
  <Text x={18} y={-10} scale={0.8} text={'💬'} anchor={{ x: 0.5, y: 0.5 }} />
patches/src/components/PixiGame.tsx CHANGED
@@ -27,9 +27,8 @@ export const PixiGame = (props: {
27
  // PIXI setup.
28
  const pixiApp = useApp();
29
  const viewportRef = useRef<Viewport | undefined>();
30
- const oauth = JSON.parse(localStorage.getItem('oauth'));
31
- const oauthToken = oauth ? oauth.userInfo.fullname : undefined;
32
- const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId: props.worldId, oauthToken }) ?? null;
33
  const humanPlayerId = [...props.game.world.players.values()].find(
34
  (p) => p.human === humanTokenIdentifier,
35
  )?.id;
 
27
  // PIXI setup.
28
  const pixiApp = useApp();
29
  const viewportRef = useRef<Viewport | undefined>();
30
+
31
+ const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId: props.worldId }) ?? null;
 
32
  const humanPlayerId = [...props.game.world.players.values()].find(
33
  (p) => p.human === humanTokenIdentifier,
34
  )?.id;
patches/src/components/PlayerDetails.tsx CHANGED
@@ -25,9 +25,7 @@ export default function PlayerDetails({
25
  setSelectedElement: SelectElement;
26
  scrollViewRef: React.RefObject<HTMLDivElement>;
27
  }) {
28
- const oauth = JSON.parse(localStorage.getItem('oauth'));
29
- const oauthToken = oauth ? oauth.userInfo.fullname : undefined;
30
- const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId, oauthToken });
31
 
32
  const players = [...game.world.players.values()];
33
  const humanPlayer = players.find((p) => p.human === humanTokenIdentifier);
 
25
  setSelectedElement: SelectElement;
26
  scrollViewRef: React.RefObject<HTMLDivElement>;
27
  }) {
28
+ const humanTokenIdentifier = useQuery(api.world.userStatus, { worldId });
 
 
29
 
30
  const players = [...game.world.players.values()];
31
  const humanPlayer = players.find((p) => p.human === humanTokenIdentifier);
patches/src/components/buttons/InteractButton.tsx CHANGED
@@ -15,9 +15,7 @@ export default function InteractButton() {
15
  const worldStatus = useQuery(api.world.defaultWorldStatus);
16
  const worldId = worldStatus?.worldId;
17
  const game = useServerGame(worldId);
18
- const oauth = JSON.parse(localStorage.getItem('oauth'));
19
- const oauthToken = oauth ? oauth.userInfo.fullname : undefined;
20
- const humanTokenIdentifier = useQuery(api.world.userStatus, worldId ? { worldId, oauthToken } : 'skip');
21
  const userPlayerId =
22
  game && [...game.world.players.values()].find((p) => p.human === humanTokenIdentifier)?.id;
23
  const join = useMutation(api.world.joinWorld);
@@ -29,7 +27,7 @@ export default function InteractButton() {
29
  async (worldId: Id<'worlds'>) => {
30
  let inputId;
31
  try {
32
- inputId = await join({ worldId, oauthToken });
33
  } catch (e: any) {
34
  if (e instanceof ConvexError) {
35
  toast.error(e.data);
@@ -43,7 +41,7 @@ export default function InteractButton() {
43
  toast.error(e.message);
44
  }
45
  },
46
- [convex, join, oauthToken],
47
  );
48
 
49
  const joinOrLeaveGame = () => {
@@ -56,7 +54,7 @@ export default function InteractButton() {
56
  }
57
  if (isPlaying) {
58
  console.log(`Leaving game for player ${userPlayerId}`);
59
- void leave({ worldId , oauthToken});
60
  } else {
61
  console.log(`Joining game`);
62
  void joinInput(worldId);
 
15
  const worldStatus = useQuery(api.world.defaultWorldStatus);
16
  const worldId = worldStatus?.worldId;
17
  const game = useServerGame(worldId);
18
+ const humanTokenIdentifier = useQuery(api.world.userStatus, worldId ? { worldId } : 'skip');
 
 
19
  const userPlayerId =
20
  game && [...game.world.players.values()].find((p) => p.human === humanTokenIdentifier)?.id;
21
  const join = useMutation(api.world.joinWorld);
 
27
  async (worldId: Id<'worlds'>) => {
28
  let inputId;
29
  try {
30
+ inputId = await join({ worldId });
31
  } catch (e: any) {
32
  if (e instanceof ConvexError) {
33
  toast.error(e.data);
 
41
  toast.error(e.message);
42
  }
43
  },
44
+ [convex],
45
  );
46
 
47
  const joinOrLeaveGame = () => {
 
54
  }
55
  if (isPlaying) {
56
  console.log(`Leaving game for player ${userPlayerId}`);
57
+ void leave({ worldId });
58
  } else {
59
  console.log(`Joining game`);
60
  void joinInput(worldId);