feat(遊戲化): 新增大廳、附身、時間流、後日談進度等遊戲化系統功能
新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
import { PrismaClient } from "../generated/client.js";
|
||||
import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
|
||||
|
||||
export function createPrismaClient(): InstanceType<typeof PrismaClient> {
|
||||
export interface CreatePrismaClientOptions {
|
||||
// T33:供測試量測查詢次數用(驗證批次查詢不隨資料量線性成長),一般呼叫端不需要帶這個選項。
|
||||
logQueries?: boolean;
|
||||
}
|
||||
|
||||
export function createPrismaClient(options: CreatePrismaClientOptions = {}): InstanceType<typeof PrismaClient> {
|
||||
const url = process.env.DATABASE_URL ?? "file:./prisma/dev.db";
|
||||
const adapter = new PrismaBetterSqlite3({ url });
|
||||
if (options.logQueries) {
|
||||
return new PrismaClient({ adapter, log: [{ emit: "event", level: "query" }] });
|
||||
}
|
||||
return new PrismaClient({ adapter });
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { CharacterBuildStatus } from "./character.js";
|
||||
|
||||
// 大廳呈現狀態:純推導型別,不落地為獨立欄位,避免與來源欄位不同步。
|
||||
export type LobbyPresenceState =
|
||||
| "BUILDING" // 建置中:CharacterBuildStatus = CANDIDATE
|
||||
| "SLEEPING" // 睡眠:ScheduleSlotStatus = SLEEP
|
||||
| "BUSY" // 忙碌:ScheduleSlotStatus = BUSY
|
||||
| "SEMI_BUSY" // 半忙碌:ScheduleSlotStatus = SEMI_BUSY
|
||||
| "IDLE" // 空閒:ScheduleSlotStatus = FREE 且未在任何非大廳房間
|
||||
| "IN_SOLO_ROOM" // 在 SOLO 房:所在房間 Room.mode = SOLO
|
||||
| "IN_GROUP_ROOM"; // 在 GROUP 房:所在房間 Room.mode = GROUP
|
||||
|
||||
// 角色目前所在「非大廳」房間的房型;恆在場的大廳(Room.mode = LOBBY)不計入本推導。
|
||||
export type LobbyPresenceRoomMode = "SOLO" | "GROUP" | null;
|
||||
|
||||
export type LobbyPresenceScheduleSlotStatus = "SLEEP" | "BUSY" | "SEMI_BUSY" | "FREE";
|
||||
|
||||
export interface LobbyPresenceInput {
|
||||
buildStatus: CharacterBuildStatus;
|
||||
scheduleSlotStatus: LobbyPresenceScheduleSlotStatus;
|
||||
roomMode: LobbyPresenceRoomMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 狀態機(優先序由上到下,命中即回傳;轉移條件即為對應來源欄位變化,見 prisma/schema.prisma:18 的
|
||||
* CharacterBuildStatus 與 prisma/schema.prisma:87 的 ScheduleSlotStatus):
|
||||
*
|
||||
* | 狀態 | 條件(資料來源) |
|
||||
* | --- | --- |
|
||||
* | BUILDING | buildStatus === "CANDIDATE" |
|
||||
* | IN_SOLO_ROOM | roomMode === "SOLO" |
|
||||
* | IN_GROUP_ROOM | roomMode === "GROUP" |
|
||||
* | SLEEPING | scheduleSlotStatus === "SLEEP" |
|
||||
* | BUSY | scheduleSlotStatus === "BUSY" |
|
||||
* | SEMI_BUSY | scheduleSlotStatus === "SEMI_BUSY" |
|
||||
* | IDLE | scheduleSlotStatus === "FREE" |
|
||||
*
|
||||
* 建置中與在房狀態優先於行程狀態:候補角色不顯示行程狀態;入房角色以房間狀態覆蓋行程呈現
|
||||
* (行程仍照常流動供自走判斷使用,僅呈現狀態以房間為準)。日後談(Work.inEpilogue)僅為
|
||||
* 附加標記、不限制呼叫,故不在此列舉。四個輸入欄位的笛卡兒積皆有對應狀態,不存在「無法判定」的組合。
|
||||
*/
|
||||
export function deriveLobbyPresenceState(input: LobbyPresenceInput): LobbyPresenceState {
|
||||
if (input.buildStatus === "CANDIDATE") return "BUILDING";
|
||||
if (input.roomMode === "SOLO") return "IN_SOLO_ROOM";
|
||||
if (input.roomMode === "GROUP") return "IN_GROUP_ROOM";
|
||||
switch (input.scheduleSlotStatus) {
|
||||
case "SLEEP":
|
||||
return "SLEEPING";
|
||||
case "BUSY":
|
||||
return "BUSY";
|
||||
case "SEMI_BUSY":
|
||||
return "SEMI_BUSY";
|
||||
case "FREE":
|
||||
return "IDLE";
|
||||
}
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from "./domain/character.js";
|
||||
export * from "./domain/memory.js";
|
||||
export * from "./domain/emotion.js";
|
||||
export * from "./domain/relationship.js";
|
||||
export * from "./domain/lobby-presence.js";
|
||||
|
||||
Reference in New Issue
Block a user