Spaces:
Sleeping
Sleeping
| import { v } from 'convex/values'; | |
| import { internal } from './_generated/api'; | |
| import { DatabaseReader, MutationCtx, mutation } from './_generated/server'; | |
| import { Descriptions } from '../data/characters'; | |
| import * as map from '../data/gentle'; | |
| import { insertInput } from './aiTown/insertInput'; | |
| import { Id } from './_generated/dataModel'; | |
| import { createEngine } from './aiTown/main'; | |
| import { ENGINE_ACTION_DURATION, MAX_NPC } from './constants'; | |
| import { assertApiKey } from './util/llm'; | |
| const init = mutation({ | |
| args: { | |
| numAgents: v.optional(v.number()), | |
| }, | |
| handler: async (ctx, args) => { | |
| assertApiKey(); | |
| const { worldStatus, engine } = await getOrCreateDefaultWorld(ctx); | |
| if (worldStatus.status !== 'running') { | |
| console.warn( | |
| `Engine ${engine._id} is not active! Run "npx convex run testing:resume" to restart it.`, | |
| ); | |
| return; | |
| } | |
| const shouldCreate = await shouldCreateAgents( | |
| ctx.db, | |
| worldStatus.worldId, | |
| worldStatus.engineId, | |
| ); | |
| if (shouldCreate) { | |
| const toCreate = args.numAgents !== undefined ? args.numAgents : MAX_NPC; //Descriptions.length; | |
| for (let i = 0; i < toCreate; i++) { | |
| await insertInput(ctx, worldStatus.worldId, 'createAgent', { | |
| descriptionIndex: i % Descriptions.length, | |
| type: 'villager', | |
| }); | |
| } | |
| } | |
| }, | |
| }); | |
| export default init; | |
| async function getOrCreateDefaultWorld(ctx: MutationCtx) { | |
| const now = Date.now(); | |
| let worldStatus = await ctx.db | |
| .query('worldStatus') | |
| .filter((q) => q.eq(q.field('isDefault'), true)) | |
| .unique(); | |
| if (worldStatus) { | |
| const engine = (await ctx.db.get(worldStatus.engineId))!; | |
| return { worldStatus, engine }; | |
| } | |
| const engineId = await createEngine(ctx); | |
| const engine = (await ctx.db.get(engineId))!; | |
| const worldId = await ctx.db.insert('worlds', { | |
| nextId: 0, | |
| agents: [], | |
| conversations: [], | |
| players: [], | |
| playersInit: [], | |
| // initialize game cycle counter | |
| gameCycle: { | |
| currentTime: 0, | |
| cycleState: 'LobbyState', | |
| cycleIndex: -1, | |
| }, | |
| gameVotes: [], | |
| llmVotes: [] | |
| }); | |
| const worldStatusId = await ctx.db.insert('worldStatus', { | |
| engineId: engineId, | |
| isDefault: true, | |
| lastViewed: now, | |
| status: 'running', | |
| worldId: worldId, | |
| }); | |
| worldStatus = (await ctx.db.get(worldStatusId))!; | |
| await ctx.db.insert('maps', { | |
| worldId, | |
| width: map.mapwidth, | |
| height: map.mapheight, | |
| tileSetUrl: map.tilesetpath, | |
| tileSetDimX: map.tilesetpxw, | |
| tileSetDimY: map.tilesetpxh, | |
| tileDim: map.tiledim, | |
| bgTiles: map.bgtiles, | |
| objectTiles: map.objmap, | |
| decorTiles: map.decors, | |
| bgTilesN: map.bgtilesN, | |
| objectTilesN: map.objmapN, | |
| decorTilesN: map.decorsN, | |
| animatedSprites: map.animatedsprites, | |
| }); | |
| await ctx.scheduler.runAfter(0, internal.aiTown.main.runStep, { | |
| worldId, | |
| generationNumber: engine.generationNumber, | |
| maxDuration: ENGINE_ACTION_DURATION, | |
| }); | |
| return { worldStatus, engine }; | |
| } | |
| async function shouldCreateAgents( | |
| db: DatabaseReader, | |
| worldId: Id<'worlds'>, | |
| engineId: Id<'engines'>, | |
| ) { | |
| const world = await db.get(worldId); | |
| if (!world) { | |
| throw new Error(`Invalid world ID: ${worldId}`); | |
| } | |
| if (world.agents.length > 0) { | |
| return false; | |
| } | |
| const unactionedJoinInputs = await db | |
| .query('inputs') | |
| .withIndex('byInputNumber', (q) => q.eq('engineId', engineId)) | |
| .order('asc') | |
| .filter((q) => q.eq(q.field('name'), 'createAgent')) | |
| .filter((q) => q.eq(q.field('returnValue'), undefined)) | |
| .collect(); | |
| if (unactionedJoinInputs.length > 0) { | |
| return false; | |
| } | |
| return true; | |
| } | |