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; | |
} | |