From 1ae2f2607a495bd11ebc8b7d7bf5f0a327a2b5a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A1=83=E6=A0=91=E5=A4=AD?= Date: Wed, 23 Sep 2026 23:27:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=BC=94=E5=87=BA=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/game-layout-ownership.md | 2 +- drizzle/0056_cultivator_stories.sql | 37 + drizzle/meta/0056_snapshot.json | 9048 +++++++++++++++++ drizzle/meta/_journal.json | 7 + .../feature/performance/PerformancePlayer.tsx | 234 + .../feature/tasks/TutorialTaskCard.tsx | 149 - .../components/game-shell/GameSceneFrame.tsx | 13 + .../components/game-shell/gameNavigation.ts | 2 + src/react-app/layouts/game-layout.tsx | 16 +- src/react-app/lib/resources/definitions.ts | 3 + .../lib/resources/playerDefinitions.ts | 9 + src/react-app/lib/router/gameShellRegistry.ts | 4 +- .../lib/router/sectOnboardingGuard.ts | 24 - src/react-app/lib/story/useStory.ts | 12 + src/react-app/lib/tasks/noviceHomeAction.ts | 118 - src/react-app/lib/tasks/taskClient.ts | 26 - src/react-app/router.tsx | 28 + .../routes/game/components/HomeView.tsx | 21 +- src/react-app/routes/game/create/route.tsx | 4 +- .../components/DungeonViewRenderer.tsx | 8 +- src/react-app/routes/game/sect/route.tsx | 17 + .../routes/game/story/preview/route.tsx | 55 + src/react-app/routes/game/story/route.tsx | 78 + .../game/tasks/components/TasksView.tsx | 32 +- src/server/lib/drizzle/schema.ts | 38 + .../lib/repositories/storyRepository.ts | 87 + .../lib/services/DungeonApplicationService.ts | 8 +- .../lib/services/StoryApplicationService.ts | 38 + src/server/lib/services/StoryService.ts | 63 + src/server/lib/services/TaskService.ts | 261 +- .../cultivator/CultivatorProfileRepository.ts | 3 + src/server/lib/services/taskDefinitions.ts | 200 +- src/server/routes/api/index.ts | 2 + src/server/routes/api/story.router.ts | 59 + .../content/performances/arrival-fall.json | 32 + src/shared/content/story/arrival.json | 23 + src/shared/contracts/resources/player.ts | 4 + src/shared/contracts/resources/registry.ts | 1 + src/shared/lib/noviceGuidance.ts | 202 - src/shared/performance/catalog.ts | 16 + src/shared/performance/interpreter.test.ts | 99 + src/shared/performance/interpreter.ts | 176 + src/shared/performance/schema.ts | 208 + src/shared/story/catalog.ts | 75 + src/shared/story/resolve.test.ts | 114 + src/shared/story/resolve.ts | 123 + src/shared/story/schema.ts | 141 + 47 files changed, 10891 insertions(+), 1029 deletions(-) create mode 100644 drizzle/0056_cultivator_stories.sql create mode 100644 drizzle/meta/0056_snapshot.json create mode 100644 src/react-app/components/feature/performance/PerformancePlayer.tsx delete mode 100644 src/react-app/components/feature/tasks/TutorialTaskCard.tsx delete mode 100644 src/react-app/lib/router/sectOnboardingGuard.ts create mode 100644 src/react-app/lib/story/useStory.ts delete mode 100644 src/react-app/lib/tasks/noviceHomeAction.ts create mode 100644 src/react-app/routes/game/story/preview/route.tsx create mode 100644 src/react-app/routes/game/story/route.tsx create mode 100644 src/server/lib/repositories/storyRepository.ts create mode 100644 src/server/lib/services/StoryApplicationService.ts create mode 100644 src/server/lib/services/StoryService.ts create mode 100644 src/server/routes/api/story.router.ts create mode 100644 src/shared/content/performances/arrival-fall.json create mode 100644 src/shared/content/story/arrival.json delete mode 100644 src/shared/lib/noviceGuidance.ts create mode 100644 src/shared/performance/catalog.ts create mode 100644 src/shared/performance/interpreter.test.ts create mode 100644 src/shared/performance/interpreter.ts create mode 100644 src/shared/performance/schema.ts create mode 100644 src/shared/story/catalog.ts create mode 100644 src/shared/story/resolve.test.ts create mode 100644 src/shared/story/resolve.ts create mode 100644 src/shared/story/schema.ts diff --git a/docs/game-layout-ownership.md b/docs/game-layout-ownership.md index 955addfb..c6944fc2 100644 --- a/docs/game-layout-ownership.md +++ b/docs/game-layout-ownership.md @@ -3,7 +3,7 @@ ## `/game` 路由归属 - `GameGenesisLayout`:`/game/create`、`/game/reincarnate` -- `GameNarrativeLayout`:`/game/sect/onboarding`、`/game/identity-reshape` 等无 HUD、无全局导航的分幕演出 +- `GameNarrativeLayout`:`/game/story`、`/game/sect/onboarding`、`/game/identity-reshape` 等无 HUD、无全局导航的分幕演出。`/game/story` 播放当前剧情演出;本地开发另有 `/game/story/preview/:scriptId` 只读预览。 - `GameViewportLayout`:常规主流程页,包括 `/game`、`/game/inventory`、`/game/retreat`、`/game/cultivator`、`/game/skills`、`/game/techniques`、`/game/artifacts`、`/game/craft*`、`/game/enlightenment*`、`/game/fate-reshape`、`/game/market*`、`/game/black-market`、`/game/auction`、`/game/mail`、`/game/world-chat`、`/game/community`、`/game/redeem`、`/game/settings/feedback`、`/game/rankings`、`/game/battle/history`、`/game/dungeon/history` - `GameActivityLayout`:`/game/sect/gate/sweep`、`/game/sect/spirit-vein/mining` 等无 HUD、无全局导航的全屏互动玩法 - `GameCombatLayout`:`/game/battle`、`/game/battle/challenge`、`/game/battle/live/:matchId`、`/game/battle/:id`、宗门任务战斗 diff --git a/drizzle/0056_cultivator_stories.sql b/drizzle/0056_cultivator_stories.sql new file mode 100644 index 00000000..8cffe755 --- /dev/null +++ b/drizzle/0056_cultivator_stories.sql @@ -0,0 +1,37 @@ +CREATE TABLE "wanjiedaoyou_cultivator_stories" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "cultivator_id" uuid NOT NULL, + "track" varchar(32) NOT NULL, + "story_id" varchar(80) NOT NULL, + "beat_id" varchar(80) NOT NULL, + "status" varchar(20) DEFAULT 'active' NOT NULL, + "acks" jsonb DEFAULT '[]'::jsonb NOT NULL, + "grants" jsonb DEFAULT '[]'::jsonb NOT NULL, + "marks" jsonb DEFAULT '[]'::jsonb NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_cultivator_stories" ADD CONSTRAINT "wanjiedaoyou_cultivator_stories_cultivator_id_wanjiedaoyou_cultivators_id_fk" FOREIGN KEY ("cultivator_id") REFERENCES "public"."wanjiedaoyou_cultivators"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "cultivator_stories_owner_track_story_unique" ON "wanjiedaoyou_cultivator_stories" USING btree ("cultivator_id","track","story_id");--> statement-breakpoint +CREATE INDEX "cultivator_stories_owner_track_status_idx" ON "wanjiedaoyou_cultivator_stories" USING btree ("cultivator_id","track","status");--> statement-breakpoint +INSERT INTO "wanjiedaoyou_cultivator_stories" ( + "cultivator_id", + "track", + "story_id", + "beat_id", + "status", + "acks", + "grants", + "marks" +) +SELECT + "id", + 'main', + 'arrival', + 'entered', + 'active', + '[]'::jsonb, + '[]'::jsonb, + '[]'::jsonb +FROM "wanjiedaoyou_cultivators"; diff --git a/drizzle/meta/0056_snapshot.json b/drizzle/meta/0056_snapshot.json new file mode 100644 index 00000000..f2b643d0 --- /dev/null +++ b/drizzle/meta/0056_snapshot.json @@ -0,0 +1,9048 @@ +{ + "id": "34b7ba53-1515-4482-a243-2b41a45696e3", + "prevId": "2e62c8e6-1bef-413c-89be-abc5c40bdcb0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.wanjiedaoyou_account_deletion_records": { + "name": "wanjiedaoyou_account_deletion_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_ids": { + "name": "cultivator_ids", + "type": "uuid[]", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "account_deletion_records_user_uidx": { + "name": "account_deletion_records_user_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_deletion_records_status_requested_idx": { + "name": "account_deletion_records_status_requested_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_admin_message_templates": { + "name": "wanjiedaoyou_admin_message_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "channel": { + "name": "channel", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "subject_template": { + "name": "subject_template", + "type": "varchar(300)", + "primaryKey": false, + "notNull": false + }, + "content_template": { + "name": "content_template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "default_payload": { + "name": "default_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "admin_templates_channel_status_created_idx": { + "name": "admin_templates_channel_status_created_idx", + "columns": [ + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_alchemy_formulas": { + "name": "wanjiedaoyou_alchemy_formulas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "family": { + "name": "family", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "blueprint": { + "name": "blueprint", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "mastery": { + "name": "mastery", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{\"level\":0,\"exp\":0}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "alchemy_formulas_cultivator_updated_idx": { + "name": "alchemy_formulas_cultivator_updated_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "alchemy_formulas_cultivator_family_idx": { + "name": "alchemy_formulas_cultivator_family_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "family", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_alchemy_formulas_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_alchemy_formulas_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_alchemy_formulas", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_app_settings": { + "name": "wanjiedaoyou_app_settings", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_auction_listings": { + "name": "wanjiedaoyou_auction_listings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "seller_id": { + "name": "seller_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "seller_name": { + "name": "seller_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "item_type": { + "name": "item_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "item_name": { + "name": "item_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "item_quality": { + "name": "item_quality", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "item_category": { + "name": "item_category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "item_snapshot": { + "name": "item_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "initial_quantity": { + "name": "initial_quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "remaining_quantity": { + "name": "remaining_quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "visibility": { + "name": "visibility", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "target_cultivator_id": { + "name": "target_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_cultivator_name": { + "name": "target_cultivator_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sold_at": { + "name": "sold_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "auction_status_expires_created_idx": { + "name": "auction_status_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_seller_status_idx": { + "name": "auction_seller_status_idx", + "columns": [ + { + "expression": "seller_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_expires_price_idx": { + "name": "auction_status_expires_price_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "price", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_type_expires_created_idx": { + "name": "auction_status_type_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_expires_item_type_idx": { + "name": "auction_status_expires_item_type_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_type_category_expires_created_idx": { + "name": "auction_status_type_category_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_type_quality_expires_created_idx": { + "name": "auction_status_type_quality_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_quality", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_item_name_expires_created_idx": { + "name": "auction_status_item_name_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_status_seller_name_expires_created_idx": { + "name": "auction_status_seller_name_expires_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seller_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "auction_visibility_target_status_idx": { + "name": "auction_visibility_target_status_idx", + "columns": [ + { + "expression": "visibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_auction_listings_seller_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_auction_listings_seller_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_auction_listings", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "seller_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_auction_listings_target_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_auction_listings_target_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_auction_listings", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "target_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_battle_records_v3": { + "name": "wanjiedaoyou_battle_records_v3", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "opponent_cultivator_id": { + "name": "opponent_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "battle_type": { + "name": "battle_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "battle_result": { + "name": "battle_result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "share_code": { + "name": "share_code", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "battle_records_v3_cultivator_created_idx": { + "name": "battle_records_v3_cultivator_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "battle_records_v3_opponent_created_idx": { + "name": "battle_records_v3_opponent_created_idx", + "columns": [ + { + "expression": "opponent_cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "battle_records_v3_user_created_idx": { + "name": "battle_records_v3_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "battle_records_v3_share_code_uidx": { + "name": "battle_records_v3_share_code_uidx", + "columns": [ + { + "expression": "share_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_battle_records_v3_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_v3_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records_v3", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_battle_records_v3_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_v3_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records_v3", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "opponent_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_battle_replay_archives": { + "name": "wanjiedaoyou_battle_replay_archives", + "schema": "", + "columns": { + "match_id": { + "name": "match_id", + "type": "varchar(120)", + "primaryKey": true, + "notNull": true + }, + "replay_version": { + "name": "replay_version", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "engine_version": { + "name": "engine_version", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "ruleset_version": { + "name": "ruleset_version", + "type": "varchar(60)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "participants": { + "name": "participants", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "replay": { + "name": "replay", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "battle_replay_archives_finished_idx": { + "name": "battle_replay_archives_finished_idx", + "columns": [ + { + "expression": "finished_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "battle_replay_archives_participants_gin_idx": { + "name": "battle_replay_archives_participants_gin_idx", + "columns": [ + { + "expression": "participants", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_bet_battles": { + "name": "wanjiedaoyou_bet_battles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "creator_id": { + "name": "creator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "creator_name": { + "name": "creator_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "min_realm": { + "name": "min_realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "max_realm": { + "name": "max_realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "taunt": { + "name": "taunt", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "creator_stake_snapshot": { + "name": "creator_stake_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "challenger_stake_snapshot": { + "name": "challenger_stake_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "challenger_id": { + "name": "challenger_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "challenger_name": { + "name": "challenger_name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "winner_cultivator_id": { + "name": "winner_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "battle_record_v3_id": { + "name": "battle_record_v3_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "matched_at": { + "name": "matched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bet_battles_status_expires_idx": { + "name": "bet_battles_status_expires_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bet_battles_creator_status_idx": { + "name": "bet_battles_creator_status_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bet_battles_status_created_idx": { + "name": "bet_battles_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_bet_battles_creator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_bet_battles_creator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_bet_battles_challenger_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_bet_battles_challenger_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "challenger_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "wanjiedaoyou_bet_battles_winner_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_bet_battles_winner_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "winner_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "wanjiedaoyou_bet_battles_battle_record_v3_id_wanjiedaoyou_battle_records_v3_id_fk": { + "name": "wanjiedaoyou_bet_battles_battle_record_v3_id_wanjiedaoyou_battle_records_v3_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_battle_records_v3", + "columnsFrom": [ + "battle_record_v3_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_breakthrough_history": { + "name": "wanjiedaoyou_breakthrough_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_realm": { + "name": "from_realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "from_stage": { + "name": "from_stage", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "to_realm": { + "name": "to_realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "to_stage": { + "name": "to_stage", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "years_spent": { + "name": "years_spent", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "story": { + "name": "story", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "breakthrough_history_cultivator_created_idx": { + "name": "breakthrough_history_cultivator_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_breakthrough_history_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_breakthrough_history_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_breakthrough_history", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_combat_replay_archives": { + "name": "wanjiedaoyou_combat_replay_archives", + "schema": "", + "columns": { + "battle_id": { + "name": "battle_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "metadata_version": { + "name": "metadata_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_type": { + "name": "source_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "battle_type": { + "name": "battle_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "engine_version": { + "name": "engine_version", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "ruleset_version": { + "name": "ruleset_version", + "type": "varchar(60)", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "replay": { + "name": "replay", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "round_count": { + "name": "round_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sides": { + "name": "sides", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[[],[]]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "combat_replay_source_idempotency_uidx": { + "name": "combat_replay_source_idempotency_uidx", + "columns": [ + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "combat_replay_finished_idx": { + "name": "combat_replay_finished_idx", + "columns": [ + { + "expression": "finished_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_combat_replay_participants": { + "name": "wanjiedaoyou_combat_replay_participants", + "schema": "", + "columns": { + "battle_id": { + "name": "battle_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "side": { + "name": "side", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "combat_replay_participant_cultivator_idx": { + "name": "combat_replay_participant_cultivator_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "battle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_combat_replay_participants_battle_id_wanjiedaoyou_combat_replay_archives_battle_id_fk": { + "name": "wanjiedaoyou_combat_replay_participants_battle_id_wanjiedaoyou_combat_replay_archives_battle_id_fk", + "tableFrom": "wanjiedaoyou_combat_replay_participants", + "tableTo": "wanjiedaoyou_combat_replay_archives", + "columnsFrom": [ + "battle_id" + ], + "columnsTo": [ + "battle_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "wanjiedaoyou_combat_replay_participants_battle_id_cultivator_id_pk": { + "name": "wanjiedaoyou_combat_replay_participants_battle_id_cultivator_id_pk", + "columns": [ + "battle_id", + "cultivator_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_consumables": { + "name": "wanjiedaoyou_consumables", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "prompt": { + "name": "prompt", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "quality": { + "name": "quality", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'凡品'" + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "stack_key": { + "name": "stack_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "consumables_cultivator_idx": { + "name": "consumables_cultivator_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "consumables_cultivator_name_quality_idx": { + "name": "consumables_cultivator_name_quality_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "quality", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "consumables_cultivator_stack_key_idx": { + "name": "consumables_cultivator_stack_key_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stack_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "consumables_cultivator_stack_unique": { + "name": "consumables_cultivator_stack_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "quality", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stack_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_consumables\".\"stack_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "consumables_score_idx": { + "name": "consumables_score_idx", + "columns": [ + { + "expression": "score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_consumables_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_consumables_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_consumables", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_creation_products": { + "name": "wanjiedaoyou_creation_products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "product_type": { + "name": "product_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "element": { + "name": "element", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "quality": { + "name": "quality", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "slot": { + "name": "slot", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "score": { + "name": "score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_equipped": { + "name": "is_equipped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "product_model": { + "name": "product_model", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "creation_products_cultivator_type_idx": { + "name": "creation_products_cultivator_type_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "product_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "creation_products_type_score_idx": { + "name": "creation_products_type_score_idx", + "columns": [ + { + "expression": "product_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "score", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "creation_products_equipped_idx": { + "name": "creation_products_equipped_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_equipped", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_creation_products_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_creation_products_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_creation_products", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_beast_fusions": { + "name": "wanjiedaoyou_cultivator_beast_fusions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "parents": { + "name": "parents", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cultivator_beast_fusions_request_unique": { + "name": "cultivator_beast_fusions_request_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_beast_fusions_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_beast_fusions_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_beast_fusions", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_beast_lineups": { + "name": "wanjiedaoyou_cultivator_beast_lineups", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "lineup": { + "name": "lineup", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "starter_claimed_at": { + "name": "starter_claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_cultivator_beast_lineups_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_beast_lineups_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_beast_lineups", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_beasts": { + "name": "wanjiedaoyou_cultivator_beasts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "individual": { + "name": "individual", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cultivator_beasts_owner_idx": { + "name": "cultivator_beasts_owner_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_beasts_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_beasts_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_beasts", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_equipment_slots": { + "name": "wanjiedaoyou_cultivator_equipment_slots", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "equipment_instance_id": { + "name": "equipment_instance_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "cultivator_equipment_instance_unique": { + "name": "cultivator_equipment_instance_unique", + "columns": [ + { + "expression": "equipment_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_equipment_slots", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_equipment_instance_id_wanjiedaoyou_inventory_items_cultivator_id_id_fk": { + "name": "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_equipment_instance_id_wanjiedaoyou_inventory_items_cultivator_id_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_equipment_slots", + "tableTo": "wanjiedaoyou_inventory_items", + "columnsFrom": [ + "cultivator_id", + "equipment_instance_id" + ], + "columnsTo": [ + "cultivator_id", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_slot_pk": { + "name": "wanjiedaoyou_cultivator_equipment_slots_cultivator_id_slot_pk", + "columns": [ + "cultivator_id", + "slot" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_friends": { + "name": "wanjiedaoyou_cultivator_friends", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "friend_cultivator_id": { + "name": "friend_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cultivator_friends_pair_uidx": { + "name": "cultivator_friends_pair_uidx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "friend_cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivator_friends_friend_idx": { + "name": "cultivator_friends_friend_idx", + "columns": [ + { + "expression": "friend_cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_friends_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_friends_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_friends", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_cultivator_friends_friend_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_friends_friend_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_friends", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "friend_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_manual_slots": { + "name": "wanjiedaoyou_cultivator_manual_slots", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slot": { + "name": "slot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "manual_id": { + "name": "manual_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "cultivator_manual_unique": { + "name": "cultivator_manual_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "manual_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_manual_slots_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_manual_slots_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_manual_slots", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "wanjiedaoyou_cultivator_manual_slots_cultivator_id_slot_pk": { + "name": "wanjiedaoyou_cultivator_manual_slots_cultivator_id_slot_pk", + "columns": [ + "cultivator_id", + "slot" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cultivator_manual_slot_valid": { + "name": "cultivator_manual_slot_valid", + "value": "\"wanjiedaoyou_cultivator_manual_slots\".\"slot\" BETWEEN 1 AND 4" + } + }, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_manual_states": { + "name": "wanjiedaoyou_cultivator_manual_states", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "learned": { + "name": "learned", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_cultivator_manual_states_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_manual_states_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_manual_states", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cultivator_manual_revision_valid": { + "name": "cultivator_manual_revision_valid", + "value": "\"wanjiedaoyou_cultivator_manual_states\".\"revision\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_stories": { + "name": "wanjiedaoyou_cultivator_stories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "track": { + "name": "track", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "story_id": { + "name": "story_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "beat_id": { + "name": "beat_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "acks": { + "name": "acks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "grants": { + "name": "grants", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "marks": { + "name": "marks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "cultivator_stories_owner_track_story_unique": { + "name": "cultivator_stories_owner_track_story_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "track", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "story_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivator_stories_owner_track_status_idx": { + "name": "cultivator_stories_owner_track_status_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "track", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_stories_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_stories_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_stories", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivator_tasks": { + "name": "wanjiedaoyou_cultivator_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "current_stage": { + "name": "current_stage", + "type": "varchar(120)", + "primaryKey": false, + "notNull": false + }, + "objectives": { + "name": "objectives", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "cultivator_tasks_cultivator_status_updated_idx": { + "name": "cultivator_tasks_cultivator_status_updated_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivator_tasks_cultivator_definition_unique": { + "name": "cultivator_tasks_cultivator_definition_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_cultivator_tasks_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_tasks_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_tasks", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_cultivators": { + "name": "wanjiedaoyou_cultivators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "gender": { + "name": "gender", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "personality": { + "name": "personality", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "background": { + "name": "background", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "player_race": { + "name": "player_race", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'human'" + }, + "race_narrative": { + "name": "race_narrative", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "realm": { + "name": "realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "realm_stage": { + "name": "realm_stage", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "age": { + "name": "age", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 18 + }, + "lifespan": { + "name": "lifespan", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 100 + }, + "closed_door_years_total": { + "name": "closed_door_years_total", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "died_at": { + "name": "died_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "vitality": { + "name": "vitality", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "strength": { + "name": "strength", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "spirit": { + "name": "spirit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "endurance": { + "name": "endurance", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "speed": { + "name": "speed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "willpower": { + "name": "willpower", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "unallocated_attribute_points": { + "name": "unallocated_attribute_points", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "spirit_stones": { + "name": "spirit_stones", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reputation": { + "name": "reputation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qi": { + "name": "qi", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 200 + }, + "qi_last_refreshed_at": { + "name": "qi_last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_yield_at": { + "name": "last_yield_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_active_at": { + "name": "last_active_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "balance_notes": { + "name": "balance_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "condition": { + "name": "condition", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "game_settings": { + "name": "game_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cultivation_progress": { + "name": "cultivation_progress", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "cultivators_user_status_updated_idx": { + "name": "cultivators_user_status_updated_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivators_status_created_idx": { + "name": "cultivators_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivators_name_idx": { + "name": "cultivators_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cultivators_status_spirit_stones_idx": { + "name": "cultivators_status_spirit_stones_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spirit_stones", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_daily_divinations": { + "name": "wanjiedaoyou_daily_divinations", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "draw_id": { + "name": "draw_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "day_key": { + "name": "day_key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "dice": { + "name": "dice", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "omen_id": { + "name": "omen_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "interpretation": { + "name": "interpretation", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fallback": { + "name": "fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "reward_granted_at": { + "name": "reward_granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_dungeon_histories": { + "name": "wanjiedaoyou_dungeon_histories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "log": { + "name": "log", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "real_gains": { + "name": "real_gains", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dungeon_histories_cultivator_created_idx": { + "name": "dungeon_histories_cultivator_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_dungeon_histories_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_dungeon_histories_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_dungeon_histories", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_dungeon_runs": { + "name": "wanjiedaoyou_dungeon_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "map_node_id": { + "name": "map_node_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true, + "default": "'EXPLORING'" + }, + "current_round": { + "name": "current_round", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "max_rounds": { + "name": "max_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "danger_score": { + "name": "danger_score", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "run_state": { + "name": "run_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cost_ledger": { + "name": "cost_ledger", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "gain_ledger": { + "name": "gain_ledger", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "pending_action": { + "name": "pending_action", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "active_battle_id": { + "name": "active_battle_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "battle_payload": { + "name": "battle_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dungeon_runs_cultivator_status_updated_idx": { + "name": "dungeon_runs_cultivator_status_updated_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dungeon_runs_status_updated_idx": { + "name": "dungeon_runs_status_updated_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_dungeon_runs_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_dungeon_runs_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_dungeon_runs", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_feedbacks": { + "name": "wanjiedaoyou_feedbacks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "feedback_user_created_at_idx": { + "name": "feedback_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "feedback_status_type_created_at_idx": { + "name": "feedback_status_type_created_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_feedbacks_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_feedbacks_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_feedbacks", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_inventory_items": { + "name": "wanjiedaoyou_inventory_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(160)", + "primaryKey": true, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "slot_index": { + "name": "slot_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "definition_id": { + "name": "definition_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "stack_key": { + "name": "stack_key", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "instance_data": { + "name": "instance_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inventory_owner_id_unique": { + "name": "inventory_owner_id_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_owner_location_idx": { + "name": "inventory_owner_location_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_stack_lookup_idx": { + "name": "inventory_stack_lookup_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "location", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stack_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inventory_bag_slot_unique": { + "name": "inventory_bag_slot_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_inventory_items\".\"location\" = 'bag'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_inventory_items_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_inventory_items_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_inventory_items", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inventory_quantity_positive": { + "name": "inventory_quantity_positive", + "value": "\"wanjiedaoyou_inventory_items\".\"quantity\" > 0 AND \"wanjiedaoyou_inventory_items\".\"revision\" >= 0" + }, + "inventory_slot_valid": { + "name": "inventory_slot_valid", + "value": "(\"wanjiedaoyou_inventory_items\".\"location\" = 'bag' AND \"wanjiedaoyou_inventory_items\".\"slot_index\" IS NOT NULL AND \"wanjiedaoyou_inventory_items\".\"slot_index\" >= 0) OR (\"wanjiedaoyou_inventory_items\".\"location\" = 'storage' AND \"wanjiedaoyou_inventory_items\".\"slot_index\" IS NULL) OR (\"wanjiedaoyou_inventory_items\".\"location\" = 'equipped' AND \"wanjiedaoyou_inventory_items\".\"slot_index\" IS NULL AND \"wanjiedaoyou_inventory_items\".\"definition_id\" = 'equipment.v6')" + } + }, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_item_library": { + "name": "wanjiedaoyou_item_library", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "item_id": { + "name": "item_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'published'" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "quality": { + "name": "quality", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "element": { + "name": "element", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "varchar(40)", + "primaryKey": false, + "notNull": false + }, + "sample_key": { + "name": "sample_key", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "editor_config": { + "name": "editor_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "item_library_item_id_unique": { + "name": "item_library_item_id_unique", + "columns": [ + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "item_library_status_type_idx": { + "name": "item_library_status_type_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "item_library_material_sample_idx": { + "name": "item_library_material_sample_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "quality", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sample_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "item_library_name_idx": { + "name": "item_library_name_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_mails": { + "name": "wanjiedaoyou_mails", + "schema": "", + "columns": { + "system_mail_campaign_id": { + "name": "system_mail_campaign_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "attachments": { + "name": "attachments", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_read": { + "name": "is_read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_claimed": { + "name": "is_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mails_campaign_cultivator_unique": { + "name": "mails_campaign_cultivator_unique", + "columns": [ + { + "expression": "system_mail_campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mails_cultivator_created_idx": { + "name": "mails_cultivator_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mails_cultivator_is_read_created_idx": { + "name": "mails_cultivator_is_read_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_read", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_mails_system_mail_campaign_id_wanjiedaoyou_system_mail_campaigns_id_fk": { + "name": "wanjiedaoyou_mails_system_mail_campaign_id_wanjiedaoyou_system_mail_campaigns_id_fk", + "tableFrom": "wanjiedaoyou_mails", + "tableTo": "wanjiedaoyou_system_mail_campaigns", + "columnsFrom": [ + "system_mail_campaign_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "wanjiedaoyou_mails_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_mails_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_mails", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_materials": { + "name": "wanjiedaoyou_materials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "rank": { + "name": "rank", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "element": { + "name": "element", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "materials_cultivator_idx": { + "name": "materials_cultivator_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "materials_cultivator_name_idx": { + "name": "materials_cultivator_name_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "materials_cultivator_name_rank_idx": { + "name": "materials_cultivator_name_rank_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "rank", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_materials_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_materials_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_materials", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_message_consumptions": { + "name": "wanjiedaoyou_message_consumptions", + "schema": "", + "columns": { + "consumer_name": { + "name": "consumer_name", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_key": { + "name": "message_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "message_consumptions_processed_idx": { + "name": "message_consumptions_processed_idx", + "columns": [ + { + "expression": "processed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "wanjiedaoyou_message_consumptions_consumer_name_message_id_pk": { + "name": "wanjiedaoyou_message_consumptions_consumer_name_message_id_pk", + "columns": [ + "consumer_name", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_player_mutation_requests": { + "name": "wanjiedaoyou_player_mutation_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "request_fingerprint": { + "name": "request_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "player_mutation_requests_scope_unique": { + "name": "player_mutation_requests_scope_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "player_mutation_requests_created_idx": { + "name": "player_mutation_requests_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_player_mutation_requests_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_player_mutation_requests_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_player_mutation_requests", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_pre_heaven_fates": { + "name": "wanjiedaoyou_pre_heaven_fates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "quality": { + "name": "quality", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "pre_heaven_fates_cultivator_idx": { + "name": "pre_heaven_fates_cultivator_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_pre_heaven_fates_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_pre_heaven_fates_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_pre_heaven_fates", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_qi_logs": { + "name": "wanjiedaoyou_qi_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "action_instance_id": { + "name": "action_instance_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "qi_cost": { + "name": "qi_cost", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qi_gain": { + "name": "qi_gain", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "qi_before": { + "name": "qi_before", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "qi_after": { + "name": "qi_after", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "qi_logs_action_instance_uidx": { + "name": "qi_logs_action_instance_uidx", + "columns": [ + { + "expression": "action_instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "qi_logs_cultivator_created_idx": { + "name": "qi_logs_cultivator_created_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "qi_logs_status_created_idx": { + "name": "qi_logs_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_qi_logs_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_qi_logs_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_qi_logs", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_redeem_code_claims": { + "name": "wanjiedaoyou_redeem_code_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "redeem_code_id": { + "name": "redeem_code_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "mail_id": { + "name": "mail_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "redeem_code_claims_code_user_unique": { + "name": "redeem_code_claims_code_user_unique", + "columns": [ + { + "expression": "redeem_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "redeem_code_claims_user_idx": { + "name": "redeem_code_claims_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "redeem_code_claims_code_idx": { + "name": "redeem_code_claims_code_idx", + "columns": [ + { + "expression": "redeem_code_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_redeem_code_claims_redeem_code_id_wanjiedaoyou_redeem_codes_id_fk": { + "name": "wanjiedaoyou_redeem_code_claims_redeem_code_id_wanjiedaoyou_redeem_codes_id_fk", + "tableFrom": "wanjiedaoyou_redeem_code_claims", + "tableTo": "wanjiedaoyou_redeem_codes", + "columnsFrom": [ + "redeem_code_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_redeem_code_claims_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_redeem_code_claims_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_redeem_code_claims", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_redeem_code_claims_mail_id_wanjiedaoyou_mails_id_fk": { + "name": "wanjiedaoyou_redeem_code_claims_mail_id_wanjiedaoyou_mails_id_fk", + "tableFrom": "wanjiedaoyou_redeem_code_claims", + "tableTo": "wanjiedaoyou_mails", + "columnsFrom": [ + "mail_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_redeem_codes": { + "name": "wanjiedaoyou_redeem_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "reward_preset_id": { + "name": "reward_preset_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "reward_attachments": { + "name": "reward_attachments", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mail_title": { + "name": "mail_title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "mail_content": { + "name": "mail_content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "total_limit": { + "name": "total_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "claimed_count": { + "name": "claimed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "redeem_codes_code_unique": { + "name": "redeem_codes_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "redeem_codes_status_created_idx": { + "name": "redeem_codes_status_created_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "redeem_codes_created_idx": { + "name": "redeem_codes_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_reputation_shop_items": { + "name": "wanjiedaoyou_reputation_shop_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "item_library_item_id": { + "name": "item_library_item_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": false + }, + "item_snapshot": { + "name": "item_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "per_user_limit": { + "name": "per_user_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reputation_shop_status_sort_idx": { + "name": "reputation_shop_status_sort_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_reputation_shop_items_item_library_item_id_wanjiedaoyou_item_library_item_id_fk": { + "name": "wanjiedaoyou_reputation_shop_items_item_library_item_id_wanjiedaoyou_item_library_item_id_fk", + "tableFrom": "wanjiedaoyou_reputation_shop_items", + "tableTo": "wanjiedaoyou_item_library", + "columnsFrom": [ + "item_library_item_id" + ], + "columnsTo": [ + "item_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_reputation_shop_purchases": { + "name": "wanjiedaoyou_reputation_shop_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "item_library_item_id": { + "name": "item_library_item_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reputation_cost": { + "name": "reputation_cost", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "purchase_week": { + "name": "purchase_week", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "reputation_shop_purchases_cultivator_item_idx": { + "name": "reputation_shop_purchases_cultivator_item_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shop_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reputation_shop_purchases_week_idx": { + "name": "reputation_shop_purchases_week_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shop_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchase_week", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "reputation_shop_purchases_created_idx": { + "name": "reputation_shop_purchases_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_reputation_shop_purchases_shop_item_id_wanjiedaoyou_reputation_shop_items_id_fk": { + "name": "wanjiedaoyou_reputation_shop_purchases_shop_item_id_wanjiedaoyou_reputation_shop_items_id_fk", + "tableFrom": "wanjiedaoyou_reputation_shop_purchases", + "tableTo": "wanjiedaoyou_reputation_shop_items", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_reputation_shop_purchases_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_reputation_shop_purchases_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_reputation_shop_purchases", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_resource_events": { + "name": "wanjiedaoyou_resource_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "scope_version": { + "name": "scope_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "resource_version": { + "name": "resource_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "actor_cultivator_id": { + "name": "actor_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "mutation_ordinal": { + "name": "mutation_ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_events_scope_version_ordinal_unique": { + "name": "resource_events_scope_version_ordinal_unique", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mutation_ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_events_scope_version_idx": { + "name": "resource_events_scope_version_idx", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mutation_ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_events_replay_idx": { + "name": "resource_events_replay_idx", + "columns": [ + { + "expression": "actor_cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mutation_ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_events_created_idx": { + "name": "resource_events_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_resource_events_scope_id_wanjiedaoyou_resource_scopes_id_fk": { + "name": "wanjiedaoyou_resource_events_scope_id_wanjiedaoyou_resource_scopes_id_fk", + "tableFrom": "wanjiedaoyou_resource_events", + "tableTo": "wanjiedaoyou_resource_scopes", + "columnsFrom": [ + "scope_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_resource_events_actor_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_resource_events_actor_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_resource_events", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "actor_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_resource_scopes": { + "name": "wanjiedaoyou_resource_scopes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scope_kind": { + "name": "scope_kind", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "scope_version": { + "name": "scope_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_scopes_kind_key_unique": { + "name": "resource_scopes_kind_key_unique", + "columns": [ + { + "expression": "scope_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_resource_versions": { + "name": "wanjiedaoyou_resource_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scope_id": { + "name": "scope_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "resource_key": { + "name": "resource_key", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_versions_scope_key_unique": { + "name": "resource_versions_scope_key_unique", + "columns": [ + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_versions_resource_idx": { + "name": "resource_versions_resource_idx", + "columns": [ + { + "expression": "resource_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_resource_versions_scope_id_wanjiedaoyou_resource_scopes_id_fk": { + "name": "wanjiedaoyou_resource_versions_scope_id_wanjiedaoyou_resource_scopes_id_fk", + "tableFrom": "wanjiedaoyou_resource_versions", + "tableTo": "wanjiedaoyou_resource_scopes", + "columnsFrom": [ + "scope_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_retreat_records": { + "name": "wanjiedaoyou_retreat_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "realm_stage": { + "name": "realm_stage", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "years": { + "name": "years", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "chance": { + "name": "chance", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "roll": { + "name": "roll", + "type": "double precision", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "modifiers": { + "name": "modifiers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "retreat_records_cultivator_timestamp_idx": { + "name": "retreat_records_cultivator_timestamp_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_retreat_records_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_retreat_records_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_retreat_records", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_combat_states": { + "name": "wanjiedaoyou_sect_combat_states", + "schema": "", + "columns": { + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "active_path_id": { + "name": "active_path_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false + }, + "meridian_depth": { + "name": "meridian_depth", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_sect_combat_states_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_combat_states_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_combat_states", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sect_combat_progress_valid": { + "name": "sect_combat_progress_valid", + "value": "\"wanjiedaoyou_sect_combat_states\".\"revision\" >= 0 AND \"wanjiedaoyou_sect_combat_states\".\"meridian_depth\" BETWEEN 0 AND 7" + } + }, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_facilities": { + "name": "wanjiedaoyou_sect_facilities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "sect_id": { + "name": "sect_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "facility_key": { + "name": "facility_key", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "progress": { + "name": "progress", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_facilities_sect_key_unique": { + "name": "sect_facilities_sect_key_unique", + "columns": [ + { + "expression": "sect_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "facility_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_memberships": { + "name": "wanjiedaoyou_sect_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sect_id": { + "name": "sect_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'prospect'" + }, + "experienced_at": { + "name": "experienced_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "contribution": { + "name": "contribution", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lifetime_contribution": { + "name": "lifetime_contribution", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disciple_rank": { + "name": "disciple_rank", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'registered'" + }, + "office": { + "name": "office", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "promoted_at": { + "name": "promoted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_memberships_cultivator_sect_unique": { + "name": "sect_memberships_cultivator_sect_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sect_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_memberships_active_cultivator_unique": { + "name": "sect_memberships_active_cultivator_unique", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_sect_memberships\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_memberships_sect_status_idx": { + "name": "sect_memberships_sect_status_idx", + "columns": [ + { + "expression": "sect_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_memberships_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sect_memberships_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sect_memberships", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_meridian_loadouts": { + "name": "wanjiedaoyou_sect_meridian_loadouts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "path_id": { + "name": "path_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "sect_meridian_membership_path_unique": { + "name": "sect_meridian_membership_path_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_meridian_loadouts_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_meridian_loadouts_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_meridian_loadouts", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_meridian_nodes": { + "name": "wanjiedaoyou_sect_meridian_nodes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "loadout_id": { + "name": "loadout_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "layer": { + "name": "layer", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "sect_meridian_loadout_node_unique": { + "name": "sect_meridian_loadout_node_unique", + "columns": [ + { + "expression": "loadout_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_meridian_loadout_layer_unique": { + "name": "sect_meridian_loadout_layer_unique", + "columns": [ + { + "expression": "loadout_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "layer", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_meridian_nodes_loadout_id_wanjiedaoyou_sect_meridian_loadouts_id_fk": { + "name": "wanjiedaoyou_sect_meridian_nodes_loadout_id_wanjiedaoyou_sect_meridian_loadouts_id_fk", + "tableFrom": "wanjiedaoyou_sect_meridian_nodes", + "tableTo": "wanjiedaoyou_sect_meridian_loadouts", + "columnsFrom": [ + "loadout_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_method_progress": { + "name": "wanjiedaoyou_sect_method_progress", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "method_id": { + "name": "method_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "sect_method_membership_method_unique": { + "name": "sect_method_membership_method_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "method_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_method_progress_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_method_progress_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_method_progress", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_shop_items": { + "name": "wanjiedaoyou_sect_shop_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "item_library_item_id": { + "name": "item_library_item_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": false + }, + "item_snapshot": { + "name": "item_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "per_user_limit": { + "name": "per_user_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_shop_status_sort_idx": { + "name": "sect_shop_status_sort_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_shop_items_item_library_item_id_wanjiedaoyou_item_library_item_id_fk": { + "name": "wanjiedaoyou_sect_shop_items_item_library_item_id_wanjiedaoyou_item_library_item_id_fk", + "tableFrom": "wanjiedaoyou_sect_shop_items", + "tableTo": "wanjiedaoyou_item_library", + "columnsFrom": [ + "item_library_item_id" + ], + "columnsTo": [ + "item_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_shop_purchases": { + "name": "wanjiedaoyou_sect_shop_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "shop_item_id": { + "name": "shop_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "item_library_item_id": { + "name": "item_library_item_id", + "type": "varchar(120)", + "primaryKey": false, + "notNull": false + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "contribution_cost": { + "name": "contribution_cost", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "purchase_week": { + "name": "purchase_week", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_shop_purchases_cultivator_item_idx": { + "name": "sect_shop_purchases_cultivator_item_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shop_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_shop_purchases_week_idx": { + "name": "sect_shop_purchases_week_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "shop_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchase_week", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_shop_purchases_created_idx": { + "name": "sect_shop_purchases_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_shop_purchases_shop_item_id_wanjiedaoyou_sect_shop_items_id_fk": { + "name": "wanjiedaoyou_sect_shop_purchases_shop_item_id_wanjiedaoyou_sect_shop_items_id_fk", + "tableFrom": "wanjiedaoyou_sect_shop_purchases", + "tableTo": "wanjiedaoyou_sect_shop_items", + "columnsFrom": [ + "shop_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_sect_shop_purchases_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sect_shop_purchases_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sect_shop_purchases", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_sect_shop_purchases_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_shop_purchases_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_shop_purchases", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_stipend_claims": { + "name": "wanjiedaoyou_sect_stipend_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "week_key": { + "name": "week_key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "spirit_stones": { + "name": "spirit_stones", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_stipend_member_week_unique": { + "name": "sect_stipend_member_week_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_stipend_claimed_idx": { + "name": "sect_stipend_claimed_idx", + "columns": [ + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_stipend_claims_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_stipend_claims_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_stipend_claims", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_task_records": { + "name": "wanjiedaoyou_sect_task_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "period_key": { + "name": "period_key", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "attempt": { + "name": "attempt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "progress": { + "name": "progress", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_task_membership_period_task_attempt_unique": { + "name": "sect_task_membership_period_task_attempt_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_task_membership_kind_period_idx": { + "name": "sect_task_membership_kind_period_idx", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_task_records_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_task_records_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_task_records", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_spirit_fields": { + "name": "wanjiedaoyou_spirit_fields", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "self_harvest_count": { + "name": "self_harvest_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_care_count": { + "name": "total_care_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "starter_claimed": { + "name": "starter_claimed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "plots": { + "name": "plots", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "spirit_fields_cultivator_uidx": { + "name": "spirit_fields_cultivator_uidx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "spirit_fields_updated_idx": { + "name": "spirit_fields_updated_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_spirit_fields_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_spirit_fields_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_spirit_fields", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_spiritual_roots": { + "name": "wanjiedaoyou_spiritual_roots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "element": { + "name": "element", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "strength": { + "name": "strength", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "marrow_wash_bonus": { + "name": "marrow_wash_bonus", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "grade": { + "name": "grade", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "spiritual_roots_cultivator_idx": { + "name": "spiritual_roots_cultivator_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_spiritual_roots_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_spiritual_roots_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_spiritual_roots", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_admin_actions": { + "name": "wanjiedaoyou_sponsorship_admin_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "admin_user_id": { + "name": "admin_user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_admin_actions_admin_created_idx": { + "name": "sponsorship_admin_actions_admin_created_idx", + "columns": [ + { + "expression": "admin_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_admin_actions_order_created_idx": { + "name": "sponsorship_admin_actions_order_created_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_admin_actions_order_id_wanjiedaoyou_sponsorship_orders_id_fk": { + "name": "wanjiedaoyou_sponsorship_admin_actions_order_id_wanjiedaoyou_sponsorship_orders_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_admin_actions", + "tableTo": "wanjiedaoyou_sponsorship_orders", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_checkout_intents": { + "name": "wanjiedaoyou_sponsorship_checkout_intents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "expected_plan_id": { + "name": "expected_plan_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "public_listing": { + "name": "public_listing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "status": { + "name": "status", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provider_order_id": { + "name": "provider_order_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "config_snapshot": { + "name": "config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_checkout_provider_order_uidx": { + "name": "sponsorship_checkout_provider_order_uidx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_checkout_user_created_idx": { + "name": "sponsorship_checkout_user_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_checkout_status_expires_idx": { + "name": "sponsorship_checkout_status_expires_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_checkout_intents_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sponsorship_checkout_intents_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_checkout_intents", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_claims": { + "name": "wanjiedaoyou_sponsorship_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "order_id": { + "name": "order_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "code_hash": { + "name": "code_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_listing": { + "name": "public_listing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "status": { + "name": "status", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "message_status": { + "name": "message_status", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "message_attempts": { + "name": "message_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_message_error": { + "name": "last_message_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_claims_order_uidx": { + "name": "sponsorship_claims_order_uidx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_claims_code_hash_uidx": { + "name": "sponsorship_claims_code_hash_uidx", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_claims_status_expires_idx": { + "name": "sponsorship_claims_status_expires_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_claims_order_id_wanjiedaoyou_sponsorship_orders_id_fk": { + "name": "wanjiedaoyou_sponsorship_claims_order_id_wanjiedaoyou_sponsorship_orders_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_claims", + "tableTo": "wanjiedaoyou_sponsorship_orders", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_sponsorship_claims_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sponsorship_claims_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_claims", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_merit_profiles": { + "name": "wanjiedaoyou_sponsorship_merit_profiles", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "highest_tier": { + "name": "highest_tier", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "merit_count": { + "name": "merit_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_supported_at": { + "name": "first_supported_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_supported_at": { + "name": "last_supported_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_merit_public_tier_first_idx": { + "name": "sponsorship_merit_public_tier_first_idx", + "columns": [ + { + "expression": "is_public", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "highest_tier", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "first_supported_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_merit_profiles_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sponsorship_merit_profiles_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_merit_profiles", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_merit_records": { + "name": "wanjiedaoyou_sponsorship_merit_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "order_id": { + "name": "order_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "supported_at": { + "name": "supported_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "mail_id": { + "name": "mail_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_merit_order_uidx": { + "name": "sponsorship_merit_order_uidx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_merit_cultivator_supported_idx": { + "name": "sponsorship_merit_cultivator_supported_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "supported_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_merit_records_order_id_wanjiedaoyou_sponsorship_orders_id_fk": { + "name": "wanjiedaoyou_sponsorship_merit_records_order_id_wanjiedaoyou_sponsorship_orders_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_merit_records", + "tableTo": "wanjiedaoyou_sponsorship_orders", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "wanjiedaoyou_sponsorship_merit_records_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_sponsorship_merit_records_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_merit_records", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_sponsorship_merit_records_mail_id_wanjiedaoyou_mails_id_fk": { + "name": "wanjiedaoyou_sponsorship_merit_records_mail_id_wanjiedaoyou_mails_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_merit_records", + "tableTo": "wanjiedaoyou_mails", + "columnsFrom": [ + "mail_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_order_snapshots": { + "name": "wanjiedaoyou_sponsorship_order_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "order_id": { + "name": "order_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "purge_after": { + "name": "purge_after", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_snapshots_order_created_idx": { + "name": "sponsorship_snapshots_order_created_idx", + "columns": [ + { + "expression": "order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_snapshots_purge_idx": { + "name": "sponsorship_snapshots_purge_idx", + "columns": [ + { + "expression": "purge_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_order_snapshots_order_id_wanjiedaoyou_sponsorship_orders_id_fk": { + "name": "wanjiedaoyou_sponsorship_order_snapshots_order_id_wanjiedaoyou_sponsorship_orders_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_order_snapshots", + "tableTo": "wanjiedaoyou_sponsorship_orders", + "columnsFrom": [ + "order_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sponsorship_orders": { + "name": "wanjiedaoyou_sponsorship_orders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "provider_order_id": { + "name": "provider_order_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "custom_order_id": { + "name": "custom_order_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "plan_id": { + "name": "plan_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "sku_id": { + "name": "sku_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "product_type": { + "name": "product_type", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_amount_fen": { + "name": "total_amount_fen", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "show_amount_fen": { + "name": "show_amount_fen", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "month": { + "name": "month", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_status": { + "name": "provider_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_created_at": { + "name": "provider_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "verification_status": { + "name": "verification_status", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "fulfillment_status": { + "name": "fulfillment_status", + "type": "varchar(24)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "resolved_tier": { + "name": "resolved_tier", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "checkout_intent_id": { + "name": "checkout_intent_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_snapshot": { + "name": "config_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error_code": { + "name": "last_error_code", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signature_verified_at": { + "name": "signature_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "fulfilled_at": { + "name": "fulfilled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "sensitive_purged_at": { + "name": "sensitive_purged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sponsorship_orders_provider_order_uidx": { + "name": "sponsorship_orders_provider_order_uidx", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_order_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_orders_verification_created_idx": { + "name": "sponsorship_orders_verification_created_idx", + "columns": [ + { + "expression": "verification_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_orders_fulfillment_created_idx": { + "name": "sponsorship_orders_fulfillment_created_idx", + "columns": [ + { + "expression": "fulfillment_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sponsorship_orders_provider_user_idx": { + "name": "sponsorship_orders_provider_user_idx", + "columns": [ + { + "expression": "provider_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sponsorship_orders_checkout_intent_id_wanjiedaoyou_sponsorship_checkout_intents_id_fk": { + "name": "wanjiedaoyou_sponsorship_orders_checkout_intent_id_wanjiedaoyou_sponsorship_checkout_intents_id_fk", + "tableFrom": "wanjiedaoyou_sponsorship_orders", + "tableTo": "wanjiedaoyou_sponsorship_checkout_intents", + "columnsFrom": [ + "checkout_intent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_system_mail_campaigns": { + "name": "wanjiedaoyou_system_mail_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "creation_fingerprint": { + "name": "creation_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reward_selections": { + "name": "reward_selections", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "system_mail_campaigns_window_idx": { + "name": "system_mail_campaigns_window_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ends_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_tower_enemy_floors": { + "name": "wanjiedaoyou_tower_enemy_floors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "season_key": { + "name": "season_key", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "realm": { + "name": "realm", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "floor": { + "name": "floor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "enemy": { + "name": "enemy", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "generated_at": { + "name": "generated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tower_enemy_floors_season_realm_floor_uidx": { + "name": "tower_enemy_floors_season_realm_floor_uidx", + "columns": [ + { + "expression": "season_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "floor", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tower_enemy_floors_realm_floor_generated_idx": { + "name": "tower_enemy_floors_realm_floor_generated_idx", + "columns": [ + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "floor", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tower_enemy_floors_season_realm_status_idx": { + "name": "tower_enemy_floors_season_realm_status_idx", + "columns": [ + { + "expression": "season_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "realm", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_tower_reward_states": { + "name": "wanjiedaoyou_tower_reward_states", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "season_key": { + "name": "season_key", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "claims": { + "name": "claims", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_tower_reward_states_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_tower_reward_states_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_tower_reward_states", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_tower_weeks": { + "name": "wanjiedaoyou_tower_weeks", + "schema": "", + "columns": { + "season_key": { + "name": "season_key", + "type": "varchar(40)", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content_version": { + "name": "content_version", + "type": "varchar(60)", + "primaryKey": false, + "notNull": true + }, + "generator_version": { + "name": "generator_version", + "type": "varchar(60)", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_transactional_messages": { + "name": "wanjiedaoyou_transactional_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "message_key": { + "name": "message_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "destination": { + "name": "destination", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "publish_attempts": { + "name": "publish_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_publish_error": { + "name": "last_publish_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "transactional_messages_dedupe_unique": { + "name": "transactional_messages_dedupe_unique", + "columns": [ + { + "expression": "message_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_transactional_messages\".\"deduplication_key\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "transactional_messages_pending_idx": { + "name": "transactional_messages_pending_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"wanjiedaoyou_transactional_messages\".\"published_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_wild_searches": { + "name": "wanjiedaoyou_wild_searches", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "encounter": { + "name": "encounter", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "prepared_battle": { + "name": "prepared_battle", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_wild_searches_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_wild_searches_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_wild_searches", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 906671eb..a324713d 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -393,6 +393,13 @@ "when": 1790173730799, "tag": "0055_system_mail_campaigns", "breakpoints": true + }, + { + "idx": 56, + "version": "7", + "when": 1790175589827, + "tag": "0056_cultivator_stories", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/react-app/components/feature/performance/PerformancePlayer.tsx b/src/react-app/components/feature/performance/PerformancePlayer.tsx new file mode 100644 index 00000000..e3e441cb --- /dev/null +++ b/src/react-app/components/feature/performance/PerformancePlayer.tsx @@ -0,0 +1,234 @@ +import { InkButton } from '@app/components/ui'; +import { GameImage } from '@app/components/ui/GameImage'; +import { useTypewriter } from '@app/lib/hooks/useTypewriter'; +import { + createPerformanceState, + currentPerformanceCue, + reducePerformance, + type PerformanceState, +} from '@shared/performance/interpreter'; +import type { + PerformanceContext, + PerformanceScript, + PerformanceTone, +} from '@shared/performance/schema'; +import { useEffect, useState } from 'react'; + +const toneWash: Record = { + mist: 'bg-cyan-950/10', + steel: 'bg-slate-950/15', + ember: 'bg-red-950/15', + stillness: 'bg-stone-950/20', +}; + +function useReducedMotion() { + const [reduced, setReduced] = useState( + () => + typeof window !== 'undefined' && + window.matchMedia('(prefers-reduced-motion: reduce)').matches, + ); + useEffect(() => { + const query = window.matchMedia('(prefers-reduced-motion: reduce)'); + const update = () => setReduced(query.matches); + update(); + query.addEventListener('change', update); + return () => query.removeEventListener('change', update); + }, []); + return reduced; +} + +export function PerformancePlayer({ + script, + context, + finalLabel, + exitLabel = '离开', + busy = false, + error, + onFinish, + onExit, +}: { + script: PerformanceScript; + context: PerformanceContext; + finalLabel: string; + exitLabel?: string; + busy?: boolean; + error?: string; + onFinish: (outcome: string) => void; + onExit: () => void; +}) { + const reducedMotion = useReducedMotion(); + const [showLog, setShowLog] = useState(false); + const [state, setState] = useState(() => + createPerformanceState(script, context), + ); + const cue = currentPerformanceCue(script, state); + const text = + cue && + (cue.type === 'narration' || cue.type === 'line' || cue.type === 'title') + ? cue.text + : ''; + const typewriter = useTypewriter({ + text, + speed: 34, + startDelay: 220, + enabled: !reducedMotion && !state.revealed && !state.ending && text.length > 0, + }); + const shown = reducedMotion || state.revealed || state.ending ? text : typewriter.displayedText; + const textDone = reducedMotion || state.revealed || typewriter.isComplete; + const endingEntry = state.log.at(-1); + + const dispatch = (next: PerformanceState) => { + setState(next); + if (next.finished && next.outcome) onFinish(next.outcome); + }; + + const advance = () => { + if (!textDone) { + typewriter.skip(); + dispatch(reducePerformance(script, context, state, { type: 'advance' })); + return; + } + dispatch(reducePerformance(script, context, state, { type: 'advance' })); + }; + + return ( +
+ {state.backdrop.src ? ( + + ) : null} +
+
+ +
+
+

{script.title}

+
+ setShowLog((open) => !open)} + className="text-[#d9cfba] hover:text-white" + > + 回顾 + + {exitLabel ? ( + + {exitLabel} + + ) : null} +
+
+ + {showLog ? ( +
    + {state.log.length === 0 ?
  1. 还没有读过的句子。
  2. : null} + {state.log.map((entry, index) => ( +
  3. + {entry.speaker ? `${entry.speaker}:` : ''} + {entry.text} +
  4. + ))} +
+ ) : null} + +
+ {state.ending && endingEntry ? ( +

+ {endingEntry.speaker ? `${endingEntry.speaker}:` : ''} + {endingEntry.text} +

+ ) : null} + + {cue?.type === 'title' ? ( +
+ {cue.kicker ? ( +

{cue.kicker}

+ ) : null} +

{shown}

+
+ ) : null} + + {cue?.type === 'narration' || cue?.type === 'line' ? ( +
+ {cue.type === 'line' ? ( +

+ {script.cast[cue.speaker]?.name} +

+ ) : null} +

+ {shown} + {!reducedMotion && typewriter.isRunning ? ( + ▌ + ) : null} +

+
+ ) : null} + + {cue?.type === 'choice' ? ( +
+ {cue.options.map((option, index) => ( + + dispatch( + reducePerformance(script, context, state, { + type: 'choose', + index, + }), + ) + } + > + {option.label} + + ))} +
+ ) : null} + + {error ? ( +

+ {error} +

+ ) : null} + +
+ {state.ending ? ( + + dispatch(reducePerformance(script, context, state, { type: 'finish' })) + } + pending={busy} + pendingLabel="落字中……" + variant="primary" + className="text-[#f0c77b] hover:text-[#ffe2a4]" + > + {finalLabel} + + ) : cue?.type === 'choice' ? null : ( + + 继续 + + )} +
+
+
+
+ ); +} diff --git a/src/react-app/components/feature/tasks/TutorialTaskCard.tsx b/src/react-app/components/feature/tasks/TutorialTaskCard.tsx deleted file mode 100644 index b2f9dfa5..00000000 --- a/src/react-app/components/feature/tasks/TutorialTaskCard.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useInkUI } from '@app/components/providers/InkUIProvider'; -import { InkButton } from '@app/components/ui/InkButton'; -import { claimTaskReward } from '@app/lib/tasks/taskClient'; -import { cn } from '@shared/lib/cn'; -import type { TaskInstance } from '@shared/types/task'; -import { useState } from 'react'; -import { TaskObjectiveRow } from './TaskObjectiveRow'; - -function StatusPill({ - text, - tone, -}: { - text: string; - tone: 'ready' | 'pending' | 'done'; -}) { - return ( - - {text} - - ); -} - -export function TutorialTaskCard({ - task, - className, -}: { - task: TaskInstance; - className?: string; -}) { - const { pushToast } = useInkUI(); - const [claiming, setClaiming] = useState(false); - const currentStage = - task.snapshot.stages.find((stage) => stage.current) ?? - task.snapshot.stages[0] ?? - null; - const rewardSummary = - task.snapshot.rewardSummary ?? task.metadata.rewardSummary ?? []; - const rewardClaimedAt = - task.snapshot.rewardClaimedAt ?? task.metadata.rewardClaimedAt; - const canClaim = task.status === 'completed' && !rewardClaimedAt; - - const handleClaim = async () => { - if (!canClaim || claiming) return; - - setClaiming(true); - try { - const result = await claimTaskReward(task.id); - pushToast({ - message: `奖励邮件已送达:${result.data.rewards.join(',')}`, - tone: 'success', - }); - } catch (error) { - pushToast({ - message: error instanceof Error ? error.message : '领取奖励失败', - tone: 'danger', - }); - } finally { - setClaiming(false); - } - }; - - const statusText = rewardClaimedAt - ? '奖励已寄' - : task.status === 'completed' - ? '可领取' - : '进行中'; - const statusTone = rewardClaimedAt - ? 'done' - : task.status === 'completed' - ? 'ready' - : 'pending'; - - return ( -
-
-
-

- {task.snapshot.title} -

- -
-

- {currentStage?.description ?? task.snapshot.summary} -

-
- - {currentStage ? ( -
- {currentStage.objectives.map((objective) => ( - - ))} -
- ) : null} - - {rewardSummary.length > 0 ? ( -

- 阶段奖励:{rewardSummary.join(',')} -

- ) : null} - -
- {canClaim ? ( - - 领取奖励 - - ) : null} - {task.status !== 'completed' && currentStage - ? currentStage.links.map((link) => ( - - {link.label} - - )) - : null} - {rewardClaimedAt && currentStage - ? currentStage.links.slice(0, 2).map((link) => ( - - {link.label} - - )) - : null} -
-
- ); -} diff --git a/src/react-app/components/game-shell/GameSceneFrame.tsx b/src/react-app/components/game-shell/GameSceneFrame.tsx index ce897e44..bf1a07d9 100644 --- a/src/react-app/components/game-shell/GameSceneFrame.tsx +++ b/src/react-app/components/game-shell/GameSceneFrame.tsx @@ -1,6 +1,8 @@ +import { InkButton } from '@app/components/ui'; import { resolveGameScene } from '@app/lib/router/routeTitle'; import { cn } from '@shared/lib/cn'; import type { ReactNode } from 'react'; +import { useStory } from '@app/lib/story/useStory'; import { useMatches } from 'react-router'; import { GameLoadingState } from './GameLoadingState'; import { getGameSceneGroupTitle } from './gameNavigation'; @@ -202,6 +204,9 @@ export function GameSceneFrame({ }: GameSceneFrameProps) { const matches = useMatches(); const scene = resolveGameScene(matches); + const story = useStory(); + const storyCue = + story.story?.prompt && scene?.id === story.story.scene ? story.story : null; const sceneGroup = scene?.group ? getGameSceneGroupTitle(scene.group) : null; const header = resolveGameSceneFrameHeader({ sceneLabel: identityOverride?.label ?? scene?.label, @@ -238,6 +243,14 @@ export function GameSceneFrame({ compact={variant !== 'default'} className={contentClassName} > + {storyCue ? ( +

+ {storyCue.prompt} + + 前往 + +

+ ) : null} {children} diff --git a/src/react-app/components/game-shell/gameNavigation.ts b/src/react-app/components/game-shell/gameNavigation.ts index 95769890..8beedda5 100644 --- a/src/react-app/components/game-shell/gameNavigation.ts +++ b/src/react-app/components/game-shell/gameNavigation.ts @@ -125,6 +125,8 @@ export const gameDockGroups: GameNavGroup[] = [ expandedDockLabel: '⛰️ 宗门', }, { id: 'sect-onboarding', sceneLabel: '诸宗山门' }, + { id: 'story', sceneLabel: '入世' }, + { id: 'story-preview', sceneLabel: '演出预览' }, { id: 'sect-transfer', sceneLabel: '欺天台 · 转宗' }, { id: 'identity-reshape', sceneLabel: '改天换地' }, { id: 'sect-visit', sceneLabel: '访宗舆图' }, diff --git a/src/react-app/layouts/game-layout.tsx b/src/react-app/layouts/game-layout.tsx index 79185e65..4f2c3ef6 100644 --- a/src/react-app/layouts/game-layout.tsx +++ b/src/react-app/layouts/game-layout.tsx @@ -15,7 +15,7 @@ import { resolveRouteTitle, type GameSceneHandle, } from '@app/lib/router/routeTitle'; -import { resolveSectOnboardingRedirect } from '@app/lib/router/sectOnboardingGuard'; + import { DungeonSceneProvider } from '@app/routes/game/dungeon/dungeonScene'; import { useResolvedDungeonScene } from '@app/routes/game/dungeon/dungeonSceneContext'; import { @@ -28,7 +28,6 @@ import { type RefObject, } from 'react'; import { - Navigate, Outlet, useLoaderData, useLocation, @@ -61,7 +60,6 @@ function PlayerShell() { const note = session.data?.note; const hasActiveCultivator = Boolean(session.data?.activeCultivator); const isLoading = session.status === 'idle' || session.status === 'loading'; - const location = useLocation(); if (isLoading && !hasActiveCultivator) { return ; @@ -94,18 +92,6 @@ function PlayerShell() { ); } - const sectState = session.data?.activeCultivator?.sectId ? 'joined' : 'none'; - const redirect = resolveSectOnboardingRedirect( - location.pathname, - hasActiveCultivator, - sectState, - location.search, - ); - - if (redirect) { - return ; - } - return (
diff --git a/src/react-app/lib/resources/definitions.ts b/src/react-app/lib/resources/definitions.ts index 523f252f..2b91b68d 100644 --- a/src/react-app/lib/resources/definitions.ts +++ b/src/react-app/lib/resources/definitions.ts @@ -14,6 +14,7 @@ export { playerProgressResource, playerSessionResource, playerTaskSummaryResource, + playerStoryResource, playerTasksResource, type PlayerTasksParams, } from './playerDefinitions'; @@ -42,6 +43,7 @@ import { playerProfileResource, playerProgressResource, playerSessionResource, + playerStoryResource, playerTaskSummaryResource, playerTasksResource, } from './playerDefinitions'; @@ -66,6 +68,7 @@ export const resourceRegistry = { playerCombatV6Build: playerSectCombatResource, playerMailSummary: playerMailSummaryResource, playerTaskSummary: playerTaskSummaryResource, + playerStory: playerStoryResource, playerTasks: playerTasksResource, sectContext: sectContextResource, sectMembers: sectMembersResource, diff --git a/src/react-app/lib/resources/playerDefinitions.ts b/src/react-app/lib/resources/playerDefinitions.ts index 107cf94f..053dd6b0 100644 --- a/src/react-app/lib/resources/playerDefinitions.ts +++ b/src/react-app/lib/resources/playerDefinitions.ts @@ -194,6 +194,15 @@ export interface PlayerTasksParams { status?: TaskStatus; } +export const playerStoryResource: ResourceDefinition<'player.story', void> = { + topic: 'player.story', + resolveScope: (scopes) => resolveTopicScope('player.story', scopes), + normalizeParams: () => undefined, + load: (scope, _params, signal) => + loadResourceEndpoint('player.story', '/api/story', scope, signal), + reduce: (current, change) => defaultResourceReducer(current, change), +}; + export const playerTasksResource: ResourceDefinition< 'player.tasks', PlayerTasksParams diff --git a/src/react-app/lib/router/gameShellRegistry.ts b/src/react-app/lib/router/gameShellRegistry.ts index a0ff44ff..7f859acb 100644 --- a/src/react-app/lib/router/gameShellRegistry.ts +++ b/src/react-app/lib/router/gameShellRegistry.ts @@ -14,7 +14,9 @@ export function resolveGameShellKind(pathname: string): GameShellKind | null { if ( pathname === '/game/sect/onboarding' || - pathname === '/game/identity-reshape' + pathname === '/game/identity-reshape' || + pathname === '/game/story' || + pathname.startsWith('/game/story/preview/') ) { return 'narrative'; } diff --git a/src/react-app/lib/router/sectOnboardingGuard.ts b/src/react-app/lib/router/sectOnboardingGuard.ts deleted file mode 100644 index adc4dd25..00000000 --- a/src/react-app/lib/router/sectOnboardingGuard.ts +++ /dev/null @@ -1,24 +0,0 @@ -export type SectOnboardingState = 'loading' | 'none' | 'joined'; - -export function resolveSectOnboardingRedirect( - pathname: string, - hasActiveCultivator: boolean, - state: SectOnboardingState, - search = '', -): string | null { - const onboardingPath = '/game/sect/onboarding'; - const normalizedPath = pathname.replace(/\/+$/, '') || '/'; - const searchParams = new URLSearchParams(search); - const hasSelectedSect = Boolean(searchParams.get('sectId')); - if (!hasActiveCultivator || state === 'loading') return null; - if (state === 'none' && normalizedPath !== onboardingPath) - return onboardingPath; - if ( - state === 'joined' && - normalizedPath === onboardingPath && - !hasSelectedSect - ) { - return '/game/sect'; - } - return null; -} diff --git a/src/react-app/lib/story/useStory.ts b/src/react-app/lib/story/useStory.ts new file mode 100644 index 00000000..a0ac3726 --- /dev/null +++ b/src/react-app/lib/story/useStory.ts @@ -0,0 +1,12 @@ +import { playerStoryResource } from '@app/lib/resources/definitions'; +import { useResource } from '@app/lib/resources/hooks'; + +export function useStory(enabled = true) { + const query = useResource(playerStoryResource, undefined, enabled); + return { + story: query.data, + loading: query.loading, + error: query.error, + reload: query.reload, + }; +} diff --git a/src/react-app/lib/tasks/noviceHomeAction.ts b/src/react-app/lib/tasks/noviceHomeAction.ts deleted file mode 100644 index 03bce05e..00000000 --- a/src/react-app/lib/tasks/noviceHomeAction.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { - NOVICE_DUNGEON_RESOURCE_THRESHOLD, - type NoviceReadinessResource, -} from '@shared/lib/noviceGuidance'; -import type { TaskInstance } from '@shared/types/task'; -import { findNextTutorialTask } from './taskClient'; - -export interface NoviceHomeAction { - title: string; - summary: string; - href: string; - label: string; -} - -export interface NoviceHomeActionInput { - tasks: TaskInstance[]; - hp?: NoviceReadinessResource | null; - mp?: NoviceReadinessResource | null; -} - -function percent(resource?: NoviceReadinessResource | null): number { - if (!resource) return 100; - const max = Math.max(1, Math.floor(resource.max)); - const current = Math.max(0, Math.min(max, Math.floor(resource.current))); - return Math.floor((current / max) * 100); -} - -function hasLowDungeonResource( - hp?: NoviceReadinessResource | null, - mp?: NoviceReadinessResource | null, -) { - return ( - percent(hp) < NOVICE_DUNGEON_RESOURCE_THRESHOLD || - percent(mp) < NOVICE_DUNGEON_RESOURCE_THRESHOLD - ); -} - -function hasUnrecoveredResource( - hp?: NoviceReadinessResource | null, - mp?: NoviceReadinessResource | null, -) { - return percent(hp) < 100 || percent(mp) < 100; -} - -export function getNextNoviceHomeAction( - input: NoviceHomeActionInput, -): NoviceHomeAction | null { - const task = findNextTutorialTask(input.tasks); - if (!task) return null; - - const rewardClaimedAt = - task.snapshot.rewardClaimedAt ?? task.metadata.rewardClaimedAt; - const canClaim = task.status === 'completed' && !rewardClaimedAt; - - if (canClaim) { - if ( - task.definitionId === 'tutorial_first_dungeon' && - hasUnrecoveredResource(input.hp, input.mp) - ) { - return { - title: '📜 战后调息', - summary: '第一次探秘已结算,先把气血与法力恢复,再领取入门奖励。', - href: '/game/inn', - label: '调息', - }; - } - - return { - title: '📜 入门卷宗', - summary: `${task.snapshot.title}已完成,先领取奖励。`, - href: '/game/tasks', - label: '领奖', - }; - } - - if (task.definitionId === 'tutorial_starter_supply') { - return { - title: '📜 入门供给', - summary: task.snapshot.summary, - href: '/game/tasks', - label: '领取', - }; - } - - if (task.definitionId === 'tutorial_first_alchemy') { - return { - title: '📜 第一炉丹', - summary: '去炼丹房使用推荐首炉,完成青露草与凝水花的入门炼制。', - href: '/game/craft/alchemy', - label: '开炉', - }; - } - - if (task.definitionId === 'tutorial_first_dungeon') { - if (hasLowDungeonResource(input.hp, input.mp)) { - return { - title: '📜 探秘准备', - summary: '首次探秘前先把气血与法力恢复到八成以上。', - href: '/game/inn', - label: '调息', - }; - } - - return { - title: '📜 低危探秘', - summary: '状态已备好,去低危秘境学会查探、撤退与结算。入门套装建议穿戴,但不强制。', - href: '/game/dungeon', - label: '探秘', - }; - } - - return { - title: '📜 入门卷宗', - summary: task.snapshot.summary, - href: '/game/tasks', - label: '继续', - }; -} diff --git a/src/react-app/lib/tasks/taskClient.ts b/src/react-app/lib/tasks/taskClient.ts index 7cd02f12..d22831ff 100644 --- a/src/react-app/lib/tasks/taskClient.ts +++ b/src/react-app/lib/tasks/taskClient.ts @@ -4,10 +4,6 @@ import type { TaskRewardClaimResponse, } from '@shared/contracts/task'; import { getNextMajorRealm } from '@shared/lib/breakthroughPill'; -import { - TUTORIAL_TASK_ORDER, - hasClaimedTutorialReward, -} from '@shared/lib/noviceGuidance'; import type { Cultivator } from '@shared/types/cultivator'; import type { TaskInstance } from '@shared/types/task'; @@ -83,25 +79,3 @@ export function findCurrentMajorBreakthroughTask( ); } -export function findNextTutorialTask( - tasks: TaskInstance[], -): TaskInstance | null { - const tutorialByDefinition = new Map( - tasks - .filter((task) => task.category === 'tutorial') - .map((task) => [task.definitionId, task]), - ); - - for (const definitionId of TUTORIAL_TASK_ORDER) { - const task = tutorialByDefinition.get(definitionId); - if (!task) { - return null; - } - - if (!task.snapshot.isCompleted || !hasClaimedTutorialReward(task)) { - return task; - } - } - - return null; -} diff --git a/src/react-app/router.tsx b/src/react-app/router.tsx index d564ca34..c2c44abb 100644 --- a/src/react-app/router.tsx +++ b/src/react-app/router.tsx @@ -154,6 +154,34 @@ export const router = createBrowserRouter( '改天换地', )} /> + import('@app/routes/game/story/route'))} + handle={scene( + { + id: 'story', + chrome: 'immersive', + dock: 'hidden', + }, + '入世', + )} + /> + {import.meta.env.DEV ? ( + import('@app/routes/game/story/preview/route'), + )} + handle={scene( + { + id: 'story-preview', + chrome: 'immersive', + dock: 'hidden', + }, + '演出预览', + )} + /> + ) : null} (tasks ? findCurrentMajorBreakthroughTask(cultivator, tasks) : null), [cultivator, tasks], ); + const story = useStory(Boolean(cultivator)); + const storyCue = story.story?.prompt ? story.story : null; if (isLoading || tasksLoading || !tasks) { return ; @@ -138,11 +140,6 @@ export function HomeView() { const urgentItems: ReactNode[] = []; const unallocatedAttributePoints = cultivator.unallocated_attribute_points ?? 0; - const noviceAction = getNextNoviceHomeAction({ - tasks, - hp: display?.resources.hp, - mp: display?.resources.mp, - }); const hasYieldAlert = yieldHours >= 1; const hasResourceAlert = caveStatus !== null && @@ -154,15 +151,15 @@ export function HomeView() { const isMajorBreakthroughCandidate = Boolean( cultivator.realm_stage === '圆满' && getNextMajorRealm(cultivator.realm), ); - if (noviceAction) { + if (storyCue) { urgentItems.push( {noviceAction.title}} - summary={noviceAction.summary} + key="story-cue" + title={玉简} + summary={storyCue.prompt} action={ - - {noviceAction.label} + + 展开 } />, diff --git a/src/react-app/routes/game/create/route.tsx b/src/react-app/routes/game/create/route.tsx index 4edb24d9..a21f0e1f 100644 --- a/src/react-app/routes/game/create/route.tsx +++ b/src/react-app/routes/game/create/route.tsx @@ -300,10 +300,10 @@ export default function CreatePage() { await consumeResourceMutation(saveResponse); pushToast({ - message: '道友真形已落地,山门正在云外相候。', + message: '道友真形已落地,玉简正在案上显字。', tone: 'success', }); - navigate('/game/sect/onboarding', { replace: true }); + navigate('/game/story', { replace: true }); } catch (error) { const errorMessage = error instanceof Error ? error.message : '保存角色失败,请检查控制台'; diff --git a/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx b/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx index 51136027..c930c609 100644 --- a/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx +++ b/src/react-app/routes/game/dungeon/components/DungeonViewRenderer.tsx @@ -208,7 +208,6 @@ export function DungeonViewRenderer({ viewState, cultivator, displayResources, - tasks, processing, actions, onSettlementConfirm, @@ -387,9 +386,6 @@ export function DungeonViewRenderer({ const selectedNode = viewState.preSelectedNodeId ? getMapNode(viewState.preSelectedNodeId) : null; - const firstDungeonTask = tasks.find( - (task) => task.definitionId === 'tutorial_first_dungeon', - ); const selectedNodeRealm = selectedNode && 'realm_requirement' in selectedNode ? selectedNode.realm_requirement @@ -407,9 +403,7 @@ export function DungeonViewRenderer({ selectedNodeRealm, hp: displayResources.hp, mp: displayResources.mp, - firstVisit: Boolean( - firstDungeonTask && !firstDungeonTask.snapshot.isCompleted, - ), + firstVisit: false, }) : null; diff --git a/src/react-app/routes/game/sect/route.tsx b/src/react-app/routes/game/sect/route.tsx index 2caac34d..6103a214 100644 --- a/src/react-app/routes/game/sect/route.tsx +++ b/src/react-app/routes/game/sect/route.tsx @@ -6,6 +6,7 @@ import { useSectInfrastructureQuery, } from '@app/components/feature/sect/sectResources'; import { GameSceneFrame, GameSceneLoading } from '@app/components/game-shell'; +import { InkButton } from '@app/components/ui'; import { formatDocumentTitle } from '@app/lib/router/routeTitle'; import { getSectPresentation } from '@app/lib/sect/sectPresentation'; import { useMemo } from 'react'; @@ -26,6 +27,22 @@ export default function SectPage() { [infrastructure.data?.facilities], ); + if (error === '尚未拜入宗门') { + return ( + +

+ 山门没有拦你。想认一认诸宗,可以自己进去看看;眼下洞府里的路也走得通。 +

+ + 去看看山门 + +
+ ); + } + if (error) return ( (); + const script = (() => { + try { + return getPerformanceScript(scriptId); + } catch { + return null; + } + })(); + + if (!script) { + return ( +
+
+

没有这场演出。

+ navigate('/game')} className="mt-5"> + 回洞府 + +
+
+ ); + } + + const filled = fillPerformanceScript(script, { + name: '道友', + background: script.requires.includes('background') ? '预览用的来处' : '', + }); + + return ( + <> + navigate('/game')} + onFinish={setOutcome} + /> + {outcome ? ( +

+ 结果:{outcome} +

+ ) : null} + + ); +} diff --git a/src/react-app/routes/game/story/route.tsx b/src/react-app/routes/game/story/route.tsx new file mode 100644 index 00000000..7cd176f8 --- /dev/null +++ b/src/react-app/routes/game/story/route.tsx @@ -0,0 +1,78 @@ +import { PerformancePlayer } from '@app/components/feature/performance/PerformancePlayer'; +import { NarrativePerformanceLoading } from '@app/components/feature/narrative/NarrativePerformanceLoading'; +import { InkButton } from '@app/components/ui'; +import { consumeResourceMutation } from '@app/lib/resources/mutations'; +import { useCultivatorIdentity } from '@app/lib/resources/player'; +import { useStory } from '@app/lib/story/useStory'; +import { fillPerformanceScript } from '@shared/performance/schema'; +import { getPerformanceScript } from '@shared/performance/catalog'; +import { useState } from 'react'; +import { Navigate, useNavigate } from 'react-router'; + +export default function StoryRoute() { + const navigate = useNavigate(); + const story = useStory(); + const profile = useCultivatorIdentity(); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(); + const cultivator = profile.data?.cultivator; + const scriptId = story.story?.scriptId; + + if (story.loading || profile.loading) { + return ; + } + + if (story.error || profile.error || !cultivator) { + return ( +
+
+

玉简暂时读不清。

+ navigate('/game')} className="mt-5"> + 回洞府 + +
+
+ ); + } + + if (!scriptId || story.story?.kind !== 'performance') { + return ; + } + + const script = fillPerformanceScript(getPerformanceScript(scriptId), { + name: cultivator.name, + background: cultivator.background?.trim() || '尚无来处', + }); + + return ( + navigate('/game')} + onFinish={(outcome) => { + setBusy(true); + setError(undefined); + void consumeResourceMutation( + fetch(`/api/story/performances/${encodeURIComponent(scriptId)}/complete`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ outcome }), + }), + ) + .then(() => navigate('/game', { replace: true })) + .catch((reason: unknown) => { + setError(reason instanceof Error ? reason.message : '演出没能记下'); + setBusy(false); + }); + }} + /> + ); +} diff --git a/src/react-app/routes/game/tasks/components/TasksView.tsx b/src/react-app/routes/game/tasks/components/TasksView.tsx index 7aa73000..6687d82a 100644 --- a/src/react-app/routes/game/tasks/components/TasksView.tsx +++ b/src/react-app/routes/game/tasks/components/TasksView.tsx @@ -1,7 +1,5 @@ import { BreakthroughTaskCard } from '@app/components/feature/tasks/BreakthroughTaskCard'; -import { TutorialTaskCard } from '@app/components/feature/tasks/TutorialTaskCard'; import { - GameLoadingState, GameSceneFrame, GameSceneLoading, GameSceneSection, @@ -9,7 +7,6 @@ import { import { InkNotice } from '@app/components/ui'; import { useTaskList } from '@app/lib/hooks/useTaskList'; import { usePlayerSession } from '@app/lib/resources/player'; -import { findNextTutorialTask } from '@app/lib/tasks/taskClient'; export function TasksView() { const session = usePlayerSession(); @@ -32,34 +29,17 @@ export function TasksView() { const breakthroughTasks = tasks?.filter( (task) => task.category === 'breakthrough_major', ); - const nextTutorialTask = tasks ? findNextTutorialTask(tasks) : null; return ( - - {loading || !tasks ? ( - - ) : error ? ( - {error} - ) : ( -
- {nextTutorialTask ? ( - - ) : null} - {!nextTutorialTask ? ( -

- 入门卷宗已办妥。之后可按破境卷宗、宗门执事堂与洞府状态推进。 -

- ) : null} -
- )} -
+ {loading || !tasks ? ( + + ) : error ? ( + {error} + ) : null} {!loading && !error && breakthroughTasks ? ( diff --git a/src/server/lib/drizzle/schema.ts b/src/server/lib/drizzle/schema.ts index a908b1e8..f64e0233 100644 --- a/src/server/lib/drizzle/schema.ts +++ b/src/server/lib/drizzle/schema.ts @@ -1,3 +1,4 @@ +import type { StoryStatus, StoryTrack } from '@shared/story/schema'; import type { SystemMailConditions } from '@shared/contracts/systemMail'; import type { RewardSelection } from '@shared/contracts/adminRewards'; import type { ItemGrant } from '@shared/inventory'; @@ -945,6 +946,43 @@ export const cultivatorTasks = pgTable( ], ); +export const cultivatorStories = pgTable( + 'wanjiedaoyou_cultivator_stories', + { + id: uuid('id').primaryKey().defaultRandom(), + cultivatorId: uuid('cultivator_id') + .references(() => cultivators.id, { onDelete: 'cascade' }) + .notNull(), + track: varchar('track', { length: 32 }).$type().notNull(), + storyId: varchar('story_id', { length: 80 }).notNull(), + beatId: varchar('beat_id', { length: 80 }).notNull(), + status: varchar('status', { length: 20 }) + .$type() + .notNull() + .default('active'), + acks: jsonb('acks').$type().notNull().default([]), + grants: jsonb('grants').$type().notNull().default([]), + marks: jsonb('marks').$type().notNull().default([]), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at') + .defaultNow() + .notNull() + .$onUpdate(() => new Date()), + }, + (table) => [ + uniqueIndex('cultivator_stories_owner_track_story_unique').on( + table.cultivatorId, + table.track, + table.storyId, + ), + index('cultivator_stories_owner_track_status_idx').on( + table.cultivatorId, + table.track, + table.status, + ), + ], +); + /** * @deprecated V5 历史战绩。本次退役版本上线后的下一版本删除表及 schema。 * 10J 已解除战报分享、旧仓储和 retention 清理引用,仅保留历史 schema。 diff --git a/src/server/lib/repositories/storyRepository.ts b/src/server/lib/repositories/storyRepository.ts new file mode 100644 index 00000000..6e7cd344 --- /dev/null +++ b/src/server/lib/repositories/storyRepository.ts @@ -0,0 +1,87 @@ +import { + getExecutor, + type DbExecutor, + type DbTransaction, +} from '@server/lib/drizzle/db'; +import { cultivatorStories } from '@server/lib/drizzle/schema'; +import { parseStoryProgress } from '@shared/story/catalog'; +import type { StoryProgress } from '@shared/story/schema'; +import { and, eq } from 'drizzle-orm'; + +export type CultivatorStoryRecord = typeof cultivatorStories.$inferSelect; + +function executor(q?: DbExecutor | DbTransaction) { + return q ?? getExecutor(); +} + +function toProgress(row: CultivatorStoryRecord): StoryProgress { + return parseStoryProgress({ + track: row.track, + storyId: row.storyId, + beatId: row.beatId, + status: row.status, + acks: row.acks, + grants: row.grants, + marks: row.marks, + }); +} + +export async function findCultivatorStory( + cultivatorId: string, + track: StoryProgress['track'], + storyId: string, + q?: DbExecutor | DbTransaction, +): Promise { + const rows = await executor(q) + .select() + .from(cultivatorStories) + .where( + and( + eq(cultivatorStories.cultivatorId, cultivatorId), + eq(cultivatorStories.track, track), + eq(cultivatorStories.storyId, storyId), + ), + ) + .limit(1); + return rows[0] ? toProgress(rows[0]) : null; +} + +export async function insertCultivatorStory( + cultivatorId: string, + progress: StoryProgress, + q: DbTransaction, +): Promise { + await q.insert(cultivatorStories).values({ + cultivatorId, + track: progress.track, + storyId: progress.storyId, + beatId: progress.beatId, + status: progress.status, + acks: progress.acks, + grants: progress.grants, + marks: progress.marks, + }); +} + +export async function updateCultivatorStory( + cultivatorId: string, + progress: StoryProgress, + q: DbTransaction, +): Promise { + await q + .update(cultivatorStories) + .set({ + beatId: progress.beatId, + status: progress.status, + acks: progress.acks, + grants: progress.grants, + marks: progress.marks, + }) + .where( + and( + eq(cultivatorStories.cultivatorId, cultivatorId), + eq(cultivatorStories.track, progress.track), + eq(cultivatorStories.storyId, progress.storyId), + ), + ); +} diff --git a/src/server/lib/services/DungeonApplicationService.ts b/src/server/lib/services/DungeonApplicationService.ts index 4444984b..6f81533b 100644 --- a/src/server/lib/services/DungeonApplicationService.ts +++ b/src/server/lib/services/DungeonApplicationService.ts @@ -21,11 +21,11 @@ import { getMapNode, isSatelliteNode, } from '@shared/lib/game/mapSystem'; -import {} from '@shared/lib/noviceGuidance'; + import { playerCommandExecutor } from './CommandExecutors'; import { toPlayerStateMutationResponse } from './ResourceMutationResponse'; -import { TaskService } from './TaskService'; + type DungeonCommand = | { kind: 'start'; mapNodeId: string } @@ -167,8 +167,6 @@ async function assertDungeonStartReady(args: { throw new DungeonStartError('只有秘境节点可以进行副本挑战', 400); } - const isFirstDungeonTutorialActive = - await TaskService.isFirstDungeonTutorialActive(args.cultivatorId); const { player, caps } = await dungeonPlayer(args.cultivatorId); const cultivator = player.cultivator; const selectedNode = getMapNode(args.mapNodeId); @@ -200,7 +198,7 @@ async function assertDungeonStartReady(args: { selectedNodeRealm, hp: entryState.hp, mp: entryState.mp, - firstVisit: isFirstDungeonTutorialActive, + firstVisit: false, }); if (readiness.shouldBlock) { throw new DungeonStartError(readiness.reasons.join(';'), 409, readiness); diff --git a/src/server/lib/services/StoryApplicationService.ts b/src/server/lib/services/StoryApplicationService.ts new file mode 100644 index 00000000..8f100585 --- /dev/null +++ b/src/server/lib/services/StoryApplicationService.ts @@ -0,0 +1,38 @@ +import type { ResourceChangeDescriptor } from '@shared/contracts/resources'; +import type { StoryView } from '@shared/story/schema'; +import { playerCommandExecutor } from './CommandExecutors'; +import { StoryService } from './StoryService'; + +export function completeStoryPerformanceCommand(args: { + userId: string; + cultivatorId: string; + scriptId: string; + outcome: string; +}) { + return playerCommandExecutor.executeWithLock({ + userId: args.userId, + cultivatorId: args.cultivatorId, + source: 'story_performance_complete', + command: async (tx) => { + const view = await StoryService.completePerformance( + args.cultivatorId, + args.scriptId, + args.outcome, + tx, + ); + return { + result: view, + resourceChanges: [ + { + resourceTopic: 'player.story', + eventType: 'story.performance_completed', + operation: 'replace', + payload: view, + }, + ] satisfies ResourceChangeDescriptor[], + }; + }, + }); +} + +export type CompletedStoryPerformance = StoryView; diff --git a/src/server/lib/services/StoryService.ts b/src/server/lib/services/StoryService.ts new file mode 100644 index 00000000..3df57260 --- /dev/null +++ b/src/server/lib/services/StoryService.ts @@ -0,0 +1,63 @@ +import type { DbTransaction } from '@server/lib/drizzle/db'; +import { + findCultivatorStory, + insertCultivatorStory, + updateCultivatorStory, +} from '@server/lib/repositories/storyRepository'; +import { + getStoryChapter, + restingStoryProgress, +} from '@shared/story/catalog'; +import { + acknowledgePerformance, + presentStory, +} from '@shared/story/resolve'; +import { emptyStoryFacts, type StoryView } from '@shared/story/schema'; + +const MAIN_STORY_ID = 'arrival'; + +export const StoryService = { + async read(cultivatorId: string, tx: DbTransaction): Promise { + const progress = + (await findCultivatorStory(cultivatorId, 'main', MAIN_STORY_ID, tx)) ?? + restingStoryProgress(); + return presentStory( + getStoryChapter(progress.storyId), + progress, + emptyStoryFacts(), + ); + }, + + async completePerformance( + cultivatorId: string, + scriptId: string, + outcome: string, + tx: DbTransaction, + ): Promise { + const stored = await findCultivatorStory( + cultivatorId, + 'main', + MAIN_STORY_ID, + tx, + ); + const progress = stored ?? restingStoryProgress(); + const chapter = getStoryChapter(progress.storyId); + const facts = emptyStoryFacts(); + const resolved = acknowledgePerformance( + chapter, + progress, + facts, + scriptId, + outcome, + ); + if (resolved.grants.length > 0) { + throw new Error('这一幕还没有可发放的物品'); + } + if (stored) { + await updateCultivatorStory(cultivatorId, resolved.progress, tx); + } else { + await insertCultivatorStory(cultivatorId, resolved.progress, tx); + } + return presentStory(chapter, resolved.progress, facts); + }, +}; diff --git a/src/server/lib/services/TaskService.ts b/src/server/lib/services/TaskService.ts index a0e16ae5..32a0feee 100644 --- a/src/server/lib/services/TaskService.ts +++ b/src/server/lib/services/TaskService.ts @@ -14,8 +14,6 @@ import { findCultivatorTaskByDefinition, findCultivatorTaskById, listCultivatorTasks, - markTaskRewardGrantPendingForKey, - markTaskRewardGrantedForKey, updateCultivatorTask, type CultivatorTaskRecord, } from '@server/lib/repositories/taskRepository'; @@ -44,17 +42,14 @@ import type { TaskStageProgress, TaskStatus, } from '@shared/types/task'; -import { MailService } from './MailService'; import { getBreakthroughTaskDefinition, getBreakthroughTaskDefinitionByTransition, getTaskChallengeProfile, getTaskDefinition, - getTutorialTaskDefinitions, type BreakthroughTaskDefinition, type RuntimeTaskDefinition, type TaskStageTemplate, - type TutorialTaskDefinition, } from './taskDefinitions'; interface TaskServiceWriteOptions { @@ -104,14 +99,6 @@ function resolveTaskRewardMailAttachments( ): MailAttachment[] { const attachments = [...resolveTaskRewardAttachments(definition)]; - if (definition.category === 'tutorial' && definition.rewardCultivationExp) { - attachments.unshift({ - type: 'cultivation_exp', - name: '修为', - quantity: definition.rewardCultivationExp, - }); - } - return attachments; } @@ -634,14 +621,6 @@ function resolveStageLinks( }); } -function withoutRewardGrantPendingKey( - metadata: TaskInstanceMetadata, -): TaskInstanceMetadata { - const next = { ...metadata }; - delete next.rewardGrantPendingKey; - return next; -} - function buildTaskSnapshot( record: CultivatorTaskRecord, definition: RuntimeTaskDefinition, @@ -883,12 +862,6 @@ async function archiveOutdatedBreakthroughRecord( ); } -function isTutorialTaskDefinition( - definition: RuntimeTaskDefinition, -): definition is TutorialTaskDefinition { - return definition.category === 'tutorial'; -} - async function createTaskRecordIfMissing( context: TaskProgressContext, definition: RuntimeTaskDefinition, @@ -932,10 +905,9 @@ async function ensureCurrentTaskRecords( options: TaskServiceWriteOptions = {}, ): Promise { const currentMajorDefinition = getCurrentMajorDefinition(context); - const definitions: RuntimeTaskDefinition[] = [ - ...getTutorialTaskDefinitions(), - ...(currentMajorDefinition ? [currentMajorDefinition] : []), - ]; + const definitions: RuntimeTaskDefinition[] = currentMajorDefinition + ? [currentMajorDefinition] + : []; for (const definition of definitions) { await createTaskRecordIfMissing(context, definition, options); @@ -946,9 +918,10 @@ async function syncTaskRecord( context: TaskProgressContext, record: CultivatorTaskRecord, options: TaskServiceWriteOptions = {}, -): Promise { +): Promise { const definition = getTaskDefinition(record.definitionId); if (!definition) { + if (record.category === 'tutorial') return null; throw new Error(`缺少任务定义:${record.definitionId}`); } @@ -1033,12 +1006,14 @@ async function syncCultivatorTasksWithContext( }); const activeRecords = records.filter((record) => record.category !== 'daily'); const q = options.tx ?? getExecutor(); - const visibleTasks = await runDbTasks( - q, - activeRecords.map( - (record) => () => syncTaskRecord(context, record, options), - ), - ); + const visibleTasks = ( + await runDbTasks( + q, + activeRecords.map( + (record) => () => syncTaskRecord(context, record, options), + ), + ) + ).filter((task): task is TaskInstance => task !== null); if (!options.hideCompletedBreakthrough) { return visibleTasks; @@ -1056,16 +1031,6 @@ async function syncCultivatorTasksWithContext( } export const TaskService = { - async isFirstDungeonTutorialActive(cultivatorId: string): Promise { - const context = await loadTaskProgressContextOrThrow(cultivatorId); - await ensureCurrentTaskRecords(context); - const task = await findCultivatorTaskByDefinition( - cultivatorId, - 'tutorial_first_dungeon', - ); - return task?.status === 'active'; - }, - async syncCultivatorTasks( cultivatorId: string, tx?: DbTransaction, @@ -1211,107 +1176,11 @@ export const TaskService = { async recordTaskEvent( cultivatorId: string, - event: TaskEvent, + _event: TaskEvent, options: TaskServiceWriteOptions = {}, ): Promise { const context = await loadTaskProgressContextOrThrow(cultivatorId, options); await ensureCurrentTaskRecords(context, options); - - const records = await listCultivatorTasks(cultivatorId, { - q: options.tx, - }); - let changedAny = false; - - for (const originalRecord of records) { - const definition = getTaskDefinition(originalRecord.definitionId); - if (!definition || !isTutorialTaskDefinition(definition)) { - continue; - } - - let record = originalRecord; - - if (record.status !== 'active') { - continue; - } - - const nextStates = normalizeObjectiveStates(record.objectives); - let changed = false; - - for (const stage of definition.stages) { - for (const objective of stage.objectives) { - if (objective.kind !== 'event_count' || objective.event !== event) { - continue; - } - - const stateIndex = nextStates.findIndex( - (state) => state.objectiveId === objective.id, - ); - const currentState = - stateIndex >= 0 - ? nextStates[stateIndex] - : createDefaultObjectiveState(objective.id); - - if (currentState.completed) { - continue; - } - - const nextProgress = Math.min( - objective.threshold, - Math.max(0, currentState.progressValue ?? 0) + 1, - ); - const completed = nextProgress >= objective.threshold; - const nextState = completed - ? completeObjectiveState( - { - ...currentState, - objectiveId: objective.id, - }, - nextProgress, - new Date().toISOString(), - ) - : { - ...createDefaultObjectiveState(objective.id), - ...currentState, - objectiveId: objective.id, - progressValue: nextProgress, - updatedAt: new Date().toISOString(), - }; - - if (stateIndex >= 0) { - nextStates[stateIndex] = nextState; - } else { - nextStates.push(nextState); - } - changed = true; - } - } - - if (!changed) { - continue; - } - - const nextMetadata = createTaskMetadata(definition); - record = (await updateCultivatorTask( - record.id, - cultivatorId, - { - objectives: nextStates, - metadata: nextMetadata, - }, - options.tx, - )) ?? { - ...record, - objectives: nextStates, - metadata: nextMetadata, - }; - await syncTaskRecord(context, record, options); - changedAny = true; - } - - if (!changedAny) { - return syncCultivatorTasksWithContext(context, options); - } - return syncCultivatorTasksWithContext(context, options); }, @@ -1320,106 +1189,12 @@ export const TaskService = { taskId: string, tx: DbTransaction, ): Promise { - const options = { tx }; - const context = await loadTaskProgressContextOrThrow(cultivatorId, options); - await ensureCurrentTaskRecords(context, options); - const taskRecord = await findCultivatorTaskById( - cultivatorId, - taskId, - options.tx, - ); - if (!taskRecord) { - throw new Error('任务不存在'); - } - let task = await syncTaskRecord(context, taskRecord, options); - - const definition = getTaskDefinition(task.definitionId); - if (!definition || !isTutorialTaskDefinition(definition)) { - throw new Error('该任务没有可手动领取的新手奖励'); - } - if (task.status !== 'completed') { - throw new Error('任务尚未完成'); - } - const grantKey = `tutorial:${definition.id}`; - if ( - task.metadata.rewardClaimedAt || - task.metadata.rewardGrantedKey === grantKey - ) { - throw new Error('奖励已经领取'); - } - - const mailAttachments = resolveTaskRewardMailAttachments(definition); - const rewards = formatTaskRewardSummary(definition); - const claimedAt = new Date().toISOString(); - const currentMetadata = task.metadata; - const claimedTask = task; - - let grantedRecord: CultivatorTaskRecord | null = null; - const grantReward = async (tx: DbTransaction) => { - const pendingRecord = await markTaskRewardGrantPendingForKey( - taskId, - cultivatorId, - grantKey, - { - ...currentMetadata, - rewardClaimedAt: currentMetadata.rewardClaimedAt ?? claimedAt, - rewardSummary: rewards, - rewardGrantPendingKey: grantKey, - }, - tx, - ); - if (!pendingRecord) { - throw new Error('奖励已经领取'); - } - - if (mailAttachments.length > 0) { - await MailService.sendNewRewardMail( - cultivatorId, - `【任务奖励】${claimedTask.snapshot.title}`, - `道友已完成"${claimedTask.snapshot.title}",任务奖励已封入附件,请前往传音符诏领取。`, - mailAttachments, - 'reward', - tx, - ); - } - - grantedRecord = await markTaskRewardGrantedForKey( - taskId, - cultivatorId, - grantKey, - { - ...withoutRewardGrantPendingKey( - pendingRecord.metadata as TaskInstanceMetadata, - ), - rewardGrantedKey: grantKey, - }, - tx, - ); - if (!grantedRecord) { - throw new Error('奖励已经领取'); - } - }; - - await grantReward(tx); - - if (!grantedRecord) { - throw new Error('奖励已经领取'); - } - - const updatedSnapshot = buildTaskSnapshot( - grantedRecord, - definition, - context, - new Date().toISOString(), - ); - task = mapTaskInstance(grantedRecord, updatedSnapshot.snapshot); - - return { - task, - rewards, - }; + const taskRecord = await findCultivatorTaskById(cultivatorId, taskId, tx); + if (!taskRecord) throw new Error('任务不存在'); + throw new Error('该任务没有可手动领取的新手奖励'); }, + async prepareTaskChallenge( cultivatorId: string, taskId: string, diff --git a/src/server/lib/services/cultivator/CultivatorProfileRepository.ts b/src/server/lib/services/cultivator/CultivatorProfileRepository.ts index 030df87d..f7b83a0e 100644 --- a/src/server/lib/services/cultivator/CultivatorProfileRepository.ts +++ b/src/server/lib/services/cultivator/CultivatorProfileRepository.ts @@ -26,6 +26,8 @@ import { type DbTransaction, } from '../../drizzle/db'; import * as schema from '../../drizzle/schema'; +import { openingStoryProgress } from '@shared/story/catalog'; +import { insertCultivatorStory } from '@server/lib/repositories/storyRepository'; import { ConditionService } from '../ConditionService'; import { FateEngine } from '../FateEngine'; import { assertCultivatorOwnership } from './CultivatorStateRepository'; @@ -167,6 +169,7 @@ export async function createCultivator( const cultivatorRecord = cultivatorResult[0]; const cultivatorId = cultivatorRecord.id; + await insertCultivatorStory(cultivatorId, openingStoryProgress(), tx); // 2. 创建灵根 if (cultivator.spiritual_roots.length > 0) { diff --git a/src/server/lib/services/taskDefinitions.ts b/src/server/lib/services/taskDefinitions.ts index 4ff4be3d..2fcf4a53 100644 --- a/src/server/lib/services/taskDefinitions.ts +++ b/src/server/lib/services/taskDefinitions.ts @@ -36,18 +36,7 @@ export interface BreakthroughTaskDefinition extends Omit< stages: TaskStageTemplate[]; } -export interface TutorialTaskDefinition extends Omit< - TaskDefinition, - 'stages' | 'category' -> { - category: 'tutorial'; - rewardCultivationExp: number; - rewardAttachments: NonNullable; - stages: TaskStageTemplate[]; -} - -export type RuntimeTaskDefinition = - BreakthroughTaskDefinition | TutorialTaskDefinition; +export type RuntimeTaskDefinition = BreakthroughTaskDefinition; export interface TaskChallengeProfile { id: string; @@ -531,189 +520,8 @@ const breakthroughDefinitions: BreakthroughTaskDefinition[] = [ }, ]; -const tutorialDefinitions: TutorialTaskDefinition[] = [ - { - id: 'tutorial_starter_supply', - category: 'tutorial', - title: '入门供给', - summary: '先领一份洞府供给,备好第一炉丹、第一次探秘。', - rewardCultivationExp: 40, - rewardAttachments: [ - { - type: 'spirit_stones', - name: '灵石', - quantity: 5000, - }, - { - type: 'material', - name: '青露草', - quantity: 3, - data: { - name: '青露草', - type: 'herb', - rank: '凡品', - element: '木', - description: '叶尖含露,药性温和,适合作为第一炉疗伤丹的主材。', - quantity: 3, - }, - }, - { - type: 'material', - name: '凝水花', - quantity: 2, - data: { - name: '凝水花', - type: 'herb', - rank: '凡品', - element: '水', - description: '花瓣凝水成珠,能缓和炉火躁性,常用于回元与疗伤。', - quantity: 2, - }, - }, - ], - stages: [ - { - id: 'starter-supply', - title: '领取供给', - description: - '先把入门供给收入囊中。第一炉丹与低危探秘可按卷宗继续推进。', - completionText: '供给已备,可以开始熟悉洞府里的修行循环。', - links: [ - { label: '看道身状态', kind: 'cultivator' }, - { label: '去储物袋', kind: 'inventory' }, - ], - objectives: [ - { - id: 'starter-supply-ready', - kind: 'auto_complete', - title: '供给已备', - description: '入门供给已经备好,领取后会获得修为、灵石与灵材。', - }, - ], - }, - ], - }, - { - id: 'tutorial_first_alchemy', - category: 'tutorial', - title: '第一炉疗伤丹', - summary: '用温和灵草开一次炉,学会材料、丹意、消耗和成丹结果之间的关系。', - rewardCultivationExp: 40, - rewardAttachments: [ - { - type: 'spirit_stones', - name: '灵石', - quantity: 3000, - }, - { - type: 'material', - name: '赤芽果', - quantity: 2, - data: { - name: '赤芽果', - type: 'herb', - rank: '凡品', - element: '火', - description: '药力较活,少量投入可提振丹势,过量则容易使炉火躁烈。', - quantity: 2, - }, - }, - ], - stages: [ - { - id: 'first-alchemy', - title: '开炉一次', - description: - '去炼丹房选择青露草、凝水花一类温和灵材,丹意可写“疗伤回元,药性温和”。', - completionText: '第一炉已成,你已经知道炼丹要先看材料药性与丹意方向。', - links: [ - { label: '去炼丹房', kind: 'alchemy' }, - { label: '查看储物袋', kind: 'inventory' }, - ], - objectives: [ - { - id: 'first-alchemy-crafted', - kind: 'event_count', - title: '完成 1 次炼丹', - description: '成功开炉一次即可完成,不要求丹药品阶。', - event: 'alchemy_crafted', - threshold: 1, - }, - ], - }, - ], - }, - { - id: 'tutorial_first_dungeon', - category: 'tutorial', - title: '第一次低危探秘', - summary: '满状态再进低危秘境,学会查探、撤退、结算和战后恢复。', - rewardCultivationExp: 50, - rewardAttachments: [ - { - type: 'spirit_stones', - name: '灵石', - quantity: 5000, - }, - { - type: 'material', - name: '铁木枝', - quantity: 2, - data: { - name: '铁木枝', - type: 'aux', - rank: '凡品', - element: '木', - description: '木质坚韧,可作炼器辅材,也能在炼丹时稳住药路。', - quantity: 2, - }, - }, - { - type: 'material', - name: '青纹回元草', - quantity: 1, - data: { - name: '青纹回元草', - type: 'herb', - rank: '凡品', - element: '木', - description: '木气温润的灵草,可调和炉势并稳住药力。', - quantity: 1, - }, - }, - ], - stages: [ - { - id: 'first-dungeon', - title: '完成一次探秘结算', - description: - '进入云游探秘前先确认气血与法力,遇敌时先查探,危险就撤退。', - completionText: - '第一次探秘已结算,你已经走完修炼、准备、探索、恢复的基础循环,也带回了一份入门材料。', - links: [ - { label: '去云游探秘', kind: 'dungeon' }, - { label: '去灵眼之泉', kind: 'inn' }, - { label: '去练功房', kind: 'training' }, - ], - objectives: [ - { - id: 'first-dungeon-completed', - kind: 'event_count', - title: '完成 1 次探秘', - description: '完成一次云游探秘结算即可,成功撤退也能学到风险判断。', - event: 'dungeon_completed', - threshold: 1, - }, - ], - }, - ], - }, -]; -const definitions: RuntimeTaskDefinition[] = [ - ...tutorialDefinitions, - ...breakthroughDefinitions, -]; +const definitions: RuntimeTaskDefinition[] = [...breakthroughDefinitions]; const definitionMap = new Map( definitions.map((definition) => [definition.id, definition]), @@ -743,10 +551,6 @@ export function getBreakthroughTaskDefinitionByTransition( ); } -export function getTutorialTaskDefinitions() { - return tutorialDefinitions; -} - export function getTaskChallengeProfile(challengeId: string) { return challengeProfileMap.get(challengeId) ?? null; } diff --git a/src/server/routes/api/index.ts b/src/server/routes/api/index.ts index d84a3c25..63e54f4b 100644 --- a/src/server/routes/api/index.ts +++ b/src/server/routes/api/index.ts @@ -35,6 +35,7 @@ import saveCharacterRouter from '@server/routes/api/save-character.router'; import sectsRouter from '@server/routes/api/sects.router'; import spiritFieldRouter from '@server/routes/api/spirit-field.router'; import sponsorshipRouter from '@server/routes/api/sponsorship.router'; +import storyRouter from '@server/routes/api/story.router'; import tasksRouter from '@server/routes/api/tasks.router'; import towerRouter from '@server/routes/api/tower.router'; import worldChatRouter from '@server/routes/api/world-chat.router'; @@ -125,6 +126,7 @@ apiRouter.route('/realtime', realtimeRouter); apiRouter.route('/reputation-shop', reputationShopRouter); apiRouter.route('/save-character', saveCharacterRouter); apiRouter.route('/tasks', tasksRouter); +apiRouter.route('/story', storyRouter); apiRouter.route('/tower', towerRouter); apiRouter.route('/sects', sectsRouter); apiRouter.route('/sponsorship', sponsorshipRouter); diff --git a/src/server/routes/api/story.router.ts b/src/server/routes/api/story.router.ts new file mode 100644 index 00000000..3440a34f --- /dev/null +++ b/src/server/routes/api/story.router.ts @@ -0,0 +1,59 @@ +import { requireActiveCultivatorRef } from '@server/lib/hono/middleware'; +import type { AppEnv } from '@server/lib/hono/types'; +import { readResourceWithMeta } from '@server/lib/services/ResourceReadService'; +import { toPlayerStateMutationResponse } from '@server/lib/services/ResourceMutationResponse'; +import { completeStoryPerformanceCommand } from '@server/lib/services/StoryApplicationService'; +import { StoryService } from '@server/lib/services/StoryService'; +import { Hono } from 'hono'; +import { z } from 'zod'; + +const CompleteSchema = z + .object({ + outcome: z.string().trim().min(1).max(40), + }) + .strict(); + +const router = new Hono(); + +router.get('/', requireActiveCultivatorRef(), async (c) => { + const ref = c.get('activeCultivatorRef'); + if (!ref) return c.json({ error: '当前没有活跃角色' }, 404); + return c.json( + await readResourceWithMeta( + { kind: 'cultivator', id: ref.cultivatorId }, + 'player.story', + (tx) => StoryService.read(ref.cultivatorId, tx), + ), + ); +}); + +router.post('/performances/:scriptId/complete', requireActiveCultivatorRef(), async (c) => { + const user = c.get('user'); + const ref = c.get('activeCultivatorRef'); + if (!user || !ref) return c.json({ error: '当前没有活跃角色' }, 404); + const scriptId = c.req.param('scriptId'); + const parsed = CompleteSchema.safeParse(await c.req.json().catch(() => null)); + if (!parsed.success) { + return c.json( + { error: parsed.error.issues[0]?.message || '演出结果无效' }, + 400, + ); + } + const { outcome } = parsed.data; + + try { + const committed = await completeStoryPerformanceCommand({ + userId: user.id, + cultivatorId: ref.cultivatorId, + scriptId, + outcome, + }); + return c.json(toPlayerStateMutationResponse(committed)); + } catch (error) { + const message = error instanceof Error ? error.message : '演出没能记下'; + const status = message.includes('当前没有这场演出') ? 409 : 400; + return c.json({ error: message }, status); + } +}); + +export default router; diff --git a/src/shared/content/performances/arrival-fall.json b/src/shared/content/performances/arrival-fall.json new file mode 100644 index 00000000..18afe37e --- /dev/null +++ b/src/shared/content/performances/arrival-fall.json @@ -0,0 +1,32 @@ +{ + "id": "arrival-fall", + "title": "入世", + "requires": ["name", "background"], + "cast": { + "slip": { "name": "玉简" } + }, + "cues": [ + { + "type": "scene", + "src": "/assets/maps/world-overview-v1.webp", + "alt": "云海之间的山河,北地雪山与南边群岛都还笼在晨雾里", + "focus": "50% 28%", + "tone": "mist" + }, + { "type": "title", "kicker": "洞府 · 初醒", "text": "入世" }, + { + "type": "narration", + "text": "{name}在一处空着的洞府里醒来。石壁还凉,案上只有一块未具名的玉简。" + }, + { + "type": "line", + "speaker": "slip", + "text": "你写下的来处是:{background}" + }, + { + "type": "narration", + "text": "玉简暗下去。门外有风,府里的炉、榻和储物都还空着。路还长,先把这处洞府当成自己的。" + }, + { "type": "end", "outcome": "entered" } + ] +} diff --git a/src/shared/content/story/arrival.json b/src/shared/content/story/arrival.json new file mode 100644 index 00000000..bfca37dd --- /dev/null +++ b/src/shared/content/story/arrival.json @@ -0,0 +1,23 @@ +{ + "id": "arrival", + "track": "main", + "title": "入世", + "beats": [ + { + "id": "fall", + "kind": "performance", + "script": "arrival-fall", + "outcome": "entered", + "scene": "story", + "prompt": "玉简还有一段没看完。", + "href": "/game/story" + }, + { + "id": "entered", + "kind": "life", + "scene": "cave", + "prompt": "", + "href": "/game" + } + ] +} diff --git a/src/shared/contracts/resources/player.ts b/src/shared/contracts/resources/player.ts index 28e5025b..07919d25 100644 --- a/src/shared/contracts/resources/player.ts +++ b/src/shared/contracts/resources/player.ts @@ -7,6 +7,7 @@ import { REALM_VALUES, SPIRITUAL_ROOT_GRADE_VALUES, } from '@shared/types/constants'; +import { StoryViewSchema, type StoryView } from '@shared/story/schema'; import type { TaskInstance } from '@shared/types/task'; import { z } from 'zod'; import { SectCombatReadinessSchema } from '../combatV6'; @@ -33,6 +34,7 @@ export const PLAYER_RESOURCE_TOPICS = [ 'player.mail-summary', 'player.task-summary', 'player.tasks', + 'player.story', ] as const; export type PlayerResourceTopic = (typeof PLAYER_RESOURCE_TOPICS)[number]; @@ -47,6 +49,7 @@ export interface PlayerResourceDataMap { 'player.mail-summary': PlayerResourceMap['mail-summary']; 'player.task-summary': PlayerResourceMap['task-summary']; 'player.tasks': TaskInstance[]; + 'player.story': StoryView; } export type TaskResourceViewParams = { @@ -523,6 +526,7 @@ export const PLAYER_RESOURCE_DATA_SCHEMAS = { }) .strict(), 'player.tasks': z.array(taskInstanceSchema), + 'player.story': StoryViewSchema, } satisfies { [TTopic in PlayerResourceTopic]: z.ZodType; }; diff --git a/src/shared/contracts/resources/registry.ts b/src/shared/contracts/resources/registry.ts index a2d80742..1cf33168 100644 --- a/src/shared/contracts/resources/registry.ts +++ b/src/shared/contracts/resources/registry.ts @@ -59,6 +59,7 @@ export const RESOURCE_TOPIC_SCOPE_KIND = { 'player.mail-summary': 'cultivator', 'player.task-summary': 'cultivator', 'player.tasks': 'cultivator', + 'player.story': 'cultivator', 'sect.membership': 'cultivator', 'sect.members': 'sect', 'sect.contribution-ranking': 'sect', diff --git a/src/shared/lib/noviceGuidance.ts b/src/shared/lib/noviceGuidance.ts deleted file mode 100644 index 1f01585c..00000000 --- a/src/shared/lib/noviceGuidance.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { REALM_ORDER, type RealmType } from '@shared/types/constants'; -import type { - Artifact, - Consumable, - Cultivator, -} from '@shared/types/cultivator'; - -export const TUTORIAL_TASK_ORDER = [ - 'tutorial_starter_supply', - 'tutorial_first_alchemy', - 'tutorial_first_dungeon', -] as const; - -export const NOVICE_GUARD_ARTIFACT_NAME = '入门护身玉佩'; -export const NOVICE_EQUIPMENT = [ - { slot: 'weapon', name: '入门青竹剑' }, - { slot: 'armor', name: '入门护身布甲' }, - { slot: 'accessory', name: NOVICE_GUARD_ARTIFACT_NAME }, -] as const; -export const NOVICE_DUNGEON_RESOURCE_THRESHOLD = 80; - -export interface NoviceReadinessResource { - current: number; - max: number; -} - -export interface NoviceDungeonReadinessInput { - cultivator: NoviceDungeonCultivator; - selectedNodeRealm?: RealmType | null; - hp: NoviceReadinessResource; - mp: NoviceReadinessResource; - isFirstDungeonTutorialActive: boolean; - hasRecoveryPill?: boolean | null; -} - -export type NoviceDungeonCultivator = Pick & { - inventory: { - artifacts: Artifact[]; - consumables?: Consumable[]; - }; -}; - -export interface NoviceDungeonReadiness { - shouldBlock: boolean; - reasons: string[]; - hints: string[]; - hpPercent: number; - mpPercent: number; - hasNoviceGuardArtifact: boolean; - hasEquippedNoviceGuard: boolean; - missingNoviceEquipmentNames: string[]; - unequippedNoviceEquipmentNames: string[]; - hasFullNoviceEquipment: boolean; - hasEquippedFullNoviceEquipment: boolean; - hasRecoveryPill: boolean | null; -} - -function toPercent(resource: NoviceReadinessResource): number { - const max = Math.max(1, Math.floor(resource.max)); - const current = Math.max(0, Math.min(max, Math.floor(resource.current))); - return Math.floor((current / max) * 100); -} - -function isRecoveryPill(consumable: Consumable): boolean { - if (consumable.quantity <= 0 || consumable.spec.kind !== 'pill') { - return false; - } - - return consumable.spec.operations.some( - (operation) => - operation.type === 'restore_resource' && - (operation.resource === 'hp' || operation.resource === 'mp'), - ); -} - -export function hasClaimedTutorialReward(task: { - metadata?: { rewardClaimedAt?: string }; - snapshot?: { rewardClaimedAt?: string }; -}): boolean { - return Boolean(task.snapshot?.rewardClaimedAt ?? task.metadata?.rewardClaimedAt); -} - -export function getNoviceEquipmentState(cultivator: NoviceDungeonCultivator): { - missingNames: string[]; - unequippedNames: string[]; - hasFullSet: boolean; - hasEquippedFullSet: boolean; -} { - const missingNames: string[] = []; - const unequippedNames: string[] = []; - - for (const equipment of NOVICE_EQUIPMENT) { - const artifact = cultivator.inventory.artifacts.find( - (candidate) => candidate.name === equipment.name, - ); - - if (!artifact?.id) { - missingNames.push(equipment.name); - continue; - } - - if (cultivator.equipped[equipment.slot] !== artifact.id) { - unequippedNames.push(equipment.name); - } - } - - return { - missingNames, - unequippedNames, - hasFullSet: missingNames.length === 0, - hasEquippedFullSet: missingNames.length === 0 && unequippedNames.length === 0, - }; -} - -export function evaluateNoviceReadiness( - input: NoviceDungeonReadinessInput, -): NoviceDungeonReadiness { - const { cultivator, selectedNodeRealm, isFirstDungeonTutorialActive } = input; - const hpPercent = toPercent(input.hp); - const mpPercent = toPercent(input.mp); - const noviceGuardArtifact = cultivator.inventory.artifacts.find( - (artifact) => artifact.name === NOVICE_GUARD_ARTIFACT_NAME, - ); - const hasNoviceGuardArtifact = Boolean(noviceGuardArtifact); - const hasEquippedNoviceGuard = Boolean( - noviceGuardArtifact?.id && - cultivator.equipped.accessory === noviceGuardArtifact.id, - ); - const noviceEquipmentState = getNoviceEquipmentState(cultivator); - const hasRecoveryPill = - input.hasRecoveryPill !== undefined - ? input.hasRecoveryPill - : (cultivator.inventory.consumables ?? []).some(isRecoveryPill); - const reasons: string[] = []; - const hints: string[] = []; - - if (!isFirstDungeonTutorialActive) { - return { - shouldBlock: false, - reasons, - hints, - hpPercent, - mpPercent, - hasNoviceGuardArtifact, - hasEquippedNoviceGuard, - missingNoviceEquipmentNames: noviceEquipmentState.missingNames, - unequippedNoviceEquipmentNames: noviceEquipmentState.unequippedNames, - hasFullNoviceEquipment: noviceEquipmentState.hasFullSet, - hasEquippedFullNoviceEquipment: noviceEquipmentState.hasEquippedFullSet, - hasRecoveryPill, - }; - } - - if ( - selectedNodeRealm && - REALM_ORDER[selectedNodeRealm] > REALM_ORDER[cultivator.realm] - ) { - reasons.push( - `当前秘境要求${selectedNodeRealm},高于你的${cultivator.realm}境界。`, - ); - hints.push('先回地图选择不高于自身境界的低危秘境。'); - } - - if (hpPercent < NOVICE_DUNGEON_RESOURCE_THRESHOLD) { - reasons.push(`气血仅 ${hpPercent}%,低于首次探秘建议值。`); - hints.push('先去灵眼之泉调息,或服用疗伤丹后再出发。'); - } - - if (mpPercent < NOVICE_DUNGEON_RESOURCE_THRESHOLD) { - reasons.push(`法力仅 ${mpPercent}%,低于首次探秘建议值。`); - hints.push('先去灵眼之泉调息,或服用回元丹后再出发。'); - } - - if (!noviceEquipmentState.hasFullSet) { - hints.push( - `尚未找到完整入门装备:${noviceEquipmentState.missingNames.join('、')},可回任务中心确认入门供给奖励。`, - ); - } else if (!noviceEquipmentState.hasEquippedFullSet) { - hints.push( - `建议先去储物袋穿戴${noviceEquipmentState.unequippedNames.join('、')},但也可以直接开始低危探秘。`, - ); - } - - if (hasRecoveryPill === false) { - hints.push('若还没有恢复丹,可先在炼丹房完成第一炉疗伤丹。'); - } - - return { - shouldBlock: reasons.length > 0, - reasons, - hints: [...new Set(hints)], - hpPercent, - mpPercent, - hasNoviceGuardArtifact, - hasEquippedNoviceGuard, - missingNoviceEquipmentNames: noviceEquipmentState.missingNames, - unequippedNoviceEquipmentNames: noviceEquipmentState.unequippedNames, - hasFullNoviceEquipment: noviceEquipmentState.hasFullSet, - hasEquippedFullNoviceEquipment: noviceEquipmentState.hasEquippedFullSet, - hasRecoveryPill, - }; -} diff --git a/src/shared/performance/catalog.ts b/src/shared/performance/catalog.ts new file mode 100644 index 00000000..366cbeec --- /dev/null +++ b/src/shared/performance/catalog.ts @@ -0,0 +1,16 @@ +import arrivalFall from '../content/performances/arrival-fall.json'; +import { parsePerformanceScript, type PerformanceScript } from './schema'; + +const scripts = new Map([ + ['arrival-fall', parsePerformanceScript(arrivalFall)], +]); + +export function listPerformanceScripts(): PerformanceScript[] { + return [...scripts.values()]; +} + +export function getPerformanceScript(id: string): PerformanceScript { + const script = scripts.get(id); + if (!script) throw new Error(`演出不存在:${id}`); + return script; +} diff --git a/src/shared/performance/interpreter.test.ts b/src/shared/performance/interpreter.test.ts new file mode 100644 index 00000000..2607f22b --- /dev/null +++ b/src/shared/performance/interpreter.test.ts @@ -0,0 +1,99 @@ +import { describe, expect, it } from 'vitest'; +import { + createPerformanceState, + currentPerformanceCue, + reducePerformance, +} from './interpreter'; +import { + fillPerformanceScript, + parsePerformanceScript, + type PerformanceScript, +} from './schema'; + +const script = parsePerformanceScript({ + id: 'sample', + title: '试演', + requires: ['name'], + cast: { guide: { name: '引路人' } }, + cues: [ + { + type: 'scene', + src: '/assets/maps/world-overview-v1.webp', + alt: '山河', + tone: 'mist', + }, + { type: 'narration', text: '{name}停下脚步。' }, + { type: 'mark', id: 'ask' }, + { type: 'line', speaker: 'guide', text: '要入山吗?' }, + { + type: 'choice', + options: [ + { label: '入山', jump: 'enter' }, + { label: '离开', outcome: 'leave' }, + ], + }, + { type: 'mark', id: 'enter' }, + { type: 'narration', text: '山门开了。', when: { path: 'name', equals: '别人' } }, + { type: 'end', outcome: 'entered' }, + ], +}); + +function playToChoice(filled: PerformanceScript) { + const context = { name: '阿青' }; + let state = createPerformanceState(filled, context); + for (let step = 0; step < 6 && currentPerformanceCue(filled, state)?.type !== 'choice'; step += 1) { + state = reducePerformance(filled, context, state, { type: 'advance' }); + } + return state; +} + +describe('performance interpreter', () => { + it('rejects a jump that has no mark', () => { + expect(() => + parsePerformanceScript({ + id: 'bad', + title: '坏稿', + requires: [], + cast: {}, + cues: [ + { type: 'scene', src: '/a.webp', alt: '画' }, + { type: 'choice', options: [{ label: '去', jump: 'missing' }] }, + { type: 'end', outcome: 'done' }, + ], + }), + ).toThrow('演出跳转没有标记'); + }); + + it('fills declared tokens and skips a failed condition', () => { + const filled = fillPerformanceScript(script, { name: '阿青' }); + const context = { name: '阿青' }; + let state = createPerformanceState(filled, context); + expect(currentPerformanceCue(filled, state)?.type).toBe('narration'); + state = reducePerformance(filled, context, state, { type: 'advance' }); + expect(state.revealed).toBe(true); + state = playToChoice(filled); + expect(currentPerformanceCue(filled, state)?.type).toBe('choice'); + state = reducePerformance(filled, context, state, { + type: 'choose', + index: 0, + }); + expect(state.ending).toBe(true); + expect(state.log.map((entry) => entry.text)).toContain('阿青停下脚步。'); + }); + + it('returns an outcome from a choice and can restart', () => { + const filled = fillPerformanceScript(script, { name: '阿青' }); + const choosing = playToChoice(filled); + const left = reducePerformance(filled, { name: '阿青' }, choosing, { + type: 'choose', + index: 1, + }); + expect(left.finished).toBe(true); + expect(left.outcome).toBe('leave'); + const restarted = reducePerformance(filled, { name: '阿青' }, left, { + type: 'restart', + }); + expect(restarted.finished).toBe(false); + expect(restarted.cursor).toBe(1); + }); +}); diff --git a/src/shared/performance/interpreter.ts b/src/shared/performance/interpreter.ts new file mode 100644 index 00000000..e3869d4c --- /dev/null +++ b/src/shared/performance/interpreter.ts @@ -0,0 +1,176 @@ +import type { + PerformanceContext, + PerformanceCue, + PerformanceScript, + PerformanceTone, +} from './schema'; + +export interface PerformanceBackdrop { + src: string; + alt: string; + focus: string; + tone: PerformanceTone; +} + +export interface PerformanceLogEntry { + speaker?: string; + text: string; +} + +export interface PerformanceState { + cursor: number; + revealed: boolean; + ending: boolean; + finished: boolean; + outcome?: string; + backdrop: PerformanceBackdrop; + log: PerformanceLogEntry[]; +} + +export type PerformanceAction = + | { type: 'advance' } + | { type: 'choose'; index: number } + | { type: 'finish' } + | { type: 'restart' }; + +const emptyBackdrop = (): PerformanceBackdrop => ({ + src: '', + alt: '', + focus: 'center', + tone: 'mist', +}); + +function whenAllows(cue: PerformanceCue, context: PerformanceContext): boolean { + if (!('when' in cue) || !cue.when) return true; + return (context[cue.when.path] ?? '') === cue.when.equals; +} + +function isText(cue: PerformanceCue): cue is Extract< + PerformanceCue, + { type: 'title' | 'narration' | 'line' } +> { + return cue.type === 'title' || cue.type === 'narration' || cue.type === 'line'; +} + +interface LocatedCue { + cursor: number; + backdrop: PerformanceBackdrop; + revealed: boolean; + ending: boolean; +} + +function locate( + script: PerformanceScript, + context: PerformanceContext, + from: number, + backdrop: PerformanceBackdrop, +): LocatedCue { + let nextBackdrop = backdrop; + for (let cursor = from; cursor < script.cues.length; cursor += 1) { + const cue = script.cues[cursor]; + if (!cue || !whenAllows(cue, context)) continue; + if (cue.type === 'mark') continue; + if (cue.type === 'scene') { + nextBackdrop = { + src: cue.src ?? nextBackdrop.src, + alt: cue.alt ?? nextBackdrop.alt, + focus: cue.focus ?? nextBackdrop.focus, + tone: cue.tone ?? nextBackdrop.tone, + }; + continue; + } + if (cue.type === 'end') { + return { cursor, backdrop: nextBackdrop, revealed: true, ending: true }; + } + return { + cursor, + backdrop: nextBackdrop, + revealed: cue.type === 'choice', + ending: false, + }; + } + throw new Error('演出没有可停留的指令'); +} + +export function createPerformanceState( + script: PerformanceScript, + context: PerformanceContext = {}, +): PerformanceState { + const located = locate(script, context, 0, emptyBackdrop()); + return { + cursor: located.cursor, + revealed: located.revealed, + ending: located.ending, + finished: false, + backdrop: located.backdrop, + log: [], + }; +} + +function remember(script: PerformanceScript, state: PerformanceState): PerformanceLogEntry[] { + const cue = script.cues[state.cursor]; + if (!cue || !isText(cue)) return state.log; + const speaker = + cue.type === 'line' ? script.cast[cue.speaker]?.name : undefined; + return [...state.log, { speaker, text: cue.text }]; +} + +export function reducePerformance( + script: PerformanceScript, + context: PerformanceContext, + state: PerformanceState, + action: PerformanceAction, +): PerformanceState { + if (action.type === 'restart') return createPerformanceState(script, context); + if (state.finished) return state; + + const cue = script.cues[state.cursor]; + if (!cue) return state; + + if (action.type === 'finish') { + if (!state.ending || cue.type !== 'end') return state; + return { ...state, finished: true, outcome: cue.outcome }; + } + + if (action.type === 'choose') { + if (cue.type !== 'choice' || !state.revealed) return state; + const option = cue.options[action.index]; + if (!option) return state; + if (option.outcome) { + return { ...state, finished: true, outcome: option.outcome, revealed: true }; + } + const markAt = script.cues.findIndex( + (entry) => entry.type === 'mark' && entry.id === option.jump, + ); + if (markAt < 0) return state; + const located = locate(script, context, markAt + 1, state.backdrop); + return { + ...state, + cursor: located.cursor, + backdrop: located.backdrop, + revealed: located.revealed, + ending: located.ending, + log: remember(script, state), + }; + } + + if (cue.type === 'choice' || cue.type === 'end') return state; + if (!state.revealed) return { ...state, revealed: true }; + + const located = locate(script, context, state.cursor + 1, state.backdrop); + return { + ...state, + cursor: located.cursor, + backdrop: located.backdrop, + revealed: located.revealed, + ending: located.ending, + log: remember(script, state), + }; +} + +export function currentPerformanceCue( + script: PerformanceScript, + state: PerformanceState, +): PerformanceCue | null { + return script.cues[state.cursor] ?? null; +} diff --git a/src/shared/performance/schema.ts b/src/shared/performance/schema.ts new file mode 100644 index 00000000..dd7938ed --- /dev/null +++ b/src/shared/performance/schema.ts @@ -0,0 +1,208 @@ +import { z } from 'zod'; + +export const PERFORMANCE_TONES = ['mist', 'steel', 'ember', 'stillness'] as const; + +export type PerformanceTone = (typeof PERFORMANCE_TONES)[number]; + +const whenSchema = z + .object({ + path: z.string().trim().min(1).max(40), + equals: z.string().max(80), + }) + .strict(); + +const sceneCueSchema = z + .object({ + type: z.literal('scene'), + src: z.string().trim().min(1).max(240).optional(), + alt: z.string().trim().min(1).max(200).optional(), + focus: z.string().trim().min(1).max(40).optional(), + tone: z.enum(PERFORMANCE_TONES).optional(), + when: whenSchema.optional(), + }) + .strict(); + +const titleCueSchema = z + .object({ + type: z.literal('title'), + kicker: z.string().trim().min(1).max(40).optional(), + text: z.string().trim().min(1).max(40), + when: whenSchema.optional(), + }) + .strict(); + +const narrationCueSchema = z + .object({ + type: z.literal('narration'), + text: z.string().trim().min(1).max(400), + when: whenSchema.optional(), + }) + .strict(); + +const lineCueSchema = z + .object({ + type: z.literal('line'), + speaker: z.string().trim().min(1).max(40), + text: z.string().trim().min(1).max(400), + when: whenSchema.optional(), + }) + .strict(); + +const choiceOptionSchema = z + .object({ + label: z.string().trim().min(1).max(24), + jump: z.string().trim().min(1).max(40).optional(), + outcome: z.string().trim().min(1).max(40).optional(), + }) + .strict() + .superRefine((option, context) => { + const targets = Number(Boolean(option.jump)) + Number(Boolean(option.outcome)); + if (targets !== 1) { + context.addIssue({ + code: 'custom', + message: '选项必须且只能指向跳转或结果之一', + }); + } + }); + +const choiceCueSchema = z + .object({ + type: z.literal('choice'), + options: z.array(choiceOptionSchema).min(1).max(4), + when: whenSchema.optional(), + }) + .strict(); + +const markCueSchema = z + .object({ + type: z.literal('mark'), + id: z.string().trim().min(1).max(40), + }) + .strict(); + +const endCueSchema = z + .object({ + type: z.literal('end'), + outcome: z.string().trim().min(1).max(40), + when: whenSchema.optional(), + }) + .strict(); + +export const PerformanceCueSchema = z.discriminatedUnion('type', [ + sceneCueSchema, + titleCueSchema, + narrationCueSchema, + lineCueSchema, + choiceCueSchema, + markCueSchema, + endCueSchema, +]); + +export const PerformanceScriptSchema = z + .object({ + id: z.string().trim().min(1).max(80), + title: z.string().trim().min(1).max(40), + requires: z.array(z.string().trim().min(1).max(40)).max(8), + cast: z.record( + z.string().trim().min(1).max(40), + z + .object({ + name: z.string().trim().min(1).max(24), + portrait: z.string().trim().min(1).max(240).optional(), + }) + .strict(), + ), + cues: z.array(PerformanceCueSchema).min(1).max(80), + }) + .strict(); + +export type PerformanceCue = z.infer; +export type PerformanceScript = z.infer; +export type PerformanceContext = Record; + +const tokenPattern = /\{([A-Za-z0-9_]+)\}/g; + +function textOf(cue: PerformanceCue): string | null { + if (cue.type === 'narration' || cue.type === 'line' || cue.type === 'title') { + return cue.text; + } + return null; +} + +export function parsePerformanceScript(input: unknown): PerformanceScript { + const script = PerformanceScriptSchema.parse(input); + const marks = new Set(); + let sceneReady = false; + let ending = false; + + for (const cue of script.cues) { + if (cue.type === 'mark') { + if (marks.has(cue.id)) { + throw new Error(`演出标记重复:${cue.id}`); + } + marks.add(cue.id); + } + if (cue.type === 'scene' && cue.src) sceneReady = true; + if (cue.type === 'line' && !script.cast[cue.speaker]) { + throw new Error(`演出说话人未登记:${cue.speaker}`); + } + if ( + (cue.type === 'narration' || + cue.type === 'line' || + cue.type === 'title' || + cue.type === 'choice' || + cue.type === 'end') && + !sceneReady + ) { + throw new Error('演出在第一张画面之前就有正文'); + } + const text = textOf(cue); + if (text) { + for (const match of text.matchAll(tokenPattern)) { + const token = match[1]; + if (!token || !script.requires.includes(token)) { + throw new Error(`演出填词未声明:${token ?? ''}`); + } + } + } + if (cue.type === 'end') ending = true; + } + + for (const cue of script.cues) { + if (cue.type !== 'choice') continue; + for (const option of cue.options) { + if (option.jump && !marks.has(option.jump)) { + throw new Error(`演出跳转没有标记:${option.jump}`); + } + } + } + + if (!sceneReady) throw new Error('演出缺少画面'); + if (!ending) throw new Error('演出缺少结尾'); + return script; +} + +export function fillPerformanceScript( + script: PerformanceScript, + context: PerformanceContext, +): PerformanceScript { + for (const key of script.requires) { + if (!context[key]?.trim()) { + throw new Error(`演出缺少填词:${key}`); + } + } + + const fill = (text: string) => + text.replace(tokenPattern, (whole, token: string) => context[token] ?? whole); + + return { + ...script, + cues: script.cues.map((cue) => { + if (cue.type === 'narration' || cue.type === 'title') { + return { ...cue, text: fill(cue.text) }; + } + if (cue.type === 'line') return { ...cue, text: fill(cue.text) }; + return cue; + }), + }; +} diff --git a/src/shared/story/catalog.ts b/src/shared/story/catalog.ts new file mode 100644 index 00000000..ccab516d --- /dev/null +++ b/src/shared/story/catalog.ts @@ -0,0 +1,75 @@ +import arrivalChapter from '../content/story/arrival.json'; +import { getPerformanceScript } from '../performance/catalog'; +import { + StoryChapterSchema, + StoryProgressSchema, + type StoryChapter, + type StoryProgress, +} from './schema'; + +function parseChapter(input: unknown): StoryChapter { + const chapter = StoryChapterSchema.parse(input); + for (const beat of chapter.beats) { + if (beat.kind !== 'performance') continue; + const script = getPerformanceScript(beat.script); + const endings = new Set( + script.cues.flatMap((cue) => { + if (cue.type === 'end') return [cue.outcome]; + if (cue.type === 'choice') { + return cue.options.flatMap((option) => + option.outcome ? [option.outcome] : [], + ); + } + return []; + }), + ); + if (!endings.has(beat.outcome)) { + throw new Error(`剧情幕结果不属于演出:${beat.id}`); + } + } + return chapter; +} + +const chapters = new Map([ + ['arrival', parseChapter(arrivalChapter)], +]); + +export function getStoryChapter(id = 'arrival'): StoryChapter { + const chapter = chapters.get(id); + if (!chapter) throw new Error(`剧情章节不存在:${id}`); + return chapter; +} + +export function openingStoryProgress(): StoryProgress { + const chapter = getStoryChapter(); + const beat = chapter.beats[0]; + if (!beat) throw new Error('入世章节没有第一幕'); + return StoryProgressSchema.parse({ + track: chapter.track, + storyId: chapter.id, + beatId: beat.id, + status: 'active', + acks: [], + grants: [], + marks: [], + }); +} + +export function restingStoryProgress(): StoryProgress { + const chapter = getStoryChapter(); + const beat = [...chapter.beats].reverse().find((entry) => entry.kind === 'life'); + if (!beat) throw new Error('入世章节没有可停留的幕'); + return StoryProgressSchema.parse({ + track: chapter.track, + storyId: chapter.id, + beatId: beat.id, + status: 'active', + acks: [], + grants: [], + marks: [], + }); +} + +export function parseStoryProgress(input: unknown): StoryProgress { + return StoryProgressSchema.parse(input); +} diff --git a/src/shared/story/resolve.test.ts b/src/shared/story/resolve.test.ts new file mode 100644 index 00000000..638707b2 --- /dev/null +++ b/src/shared/story/resolve.test.ts @@ -0,0 +1,114 @@ +import { describe, expect, it } from 'vitest'; +import { getStoryChapter, openingStoryProgress } from './catalog'; +import { acknowledgePerformance, presentStory, resolveStory } from './resolve'; +import { + emptyStoryFacts, + type StoryChapter, + type StoryProgress, +} from './schema'; + +const chapter: StoryChapter = { + id: 'sample', + track: 'main', + title: '试章', + beats: [ + { + id: 'watch', + kind: 'performance', + script: 'sample-play', + outcome: 'go', + scene: 'story', + prompt: '先看完。', + href: '/game/story', + }, + { + id: 'craft', + kind: 'practice', + fact: 'alchemy_crafted', + scene: 'alchemy', + prompt: '去开炉。', + href: '/game/craft/alchemy', + grant: 'herbs', + }, + { + id: 'stay', + kind: 'life', + scene: 'cave', + prompt: '', + href: '/game', + when: { + fact: 'breakthrough_available', + prompt: '该破境了。', + href: '/game/tasks', + }, + }, + ], +}; + +function progress(beatId: string, extra: Partial = {}): StoryProgress { + return { + track: 'main', + storyId: 'sample', + beatId, + status: 'active', + acks: [], + grants: [], + marks: [], + ...extra, + }; +} + +describe('story resolver', () => { + it('loads the arrival chapter and opens on the first performance', () => { + const arrival = getStoryChapter(); + const opening = openingStoryProgress(); + const view = presentStory(arrival, opening, emptyStoryFacts()); + expect(opening.track).toBe('main'); + expect(view.track).toBe('main'); + expect(view.kind).toBe('performance'); + expect(view.scriptId).toBe('arrival-fall'); + expect(view.href).toBe('/game/story'); + }); + + it('advances a performance into a practice and grants once', () => { + const facts = emptyStoryFacts(); + const first = acknowledgePerformance( + chapter, + progress('watch', { marks: ['alchemy_crafted'] }), + facts, + 'sample-play', + 'go', + ); + expect(first.progress.beatId).toBe('stay'); + expect(first.grants).toEqual(['herbs']); + const again = resolveStory(chapter, first.progress, facts); + expect(again.grants).toEqual([]); + expect(again.progress.beatId).toBe('stay'); + }); + + it('keeps a life beat in place and only changes its prompt', () => { + const stayed = progress('stay'); + expect(presentStory(chapter, stayed, emptyStoryFacts()).prompt).toBe(''); + expect( + presentStory(chapter, stayed, { + ...emptyStoryFacts(), + breakthrough_available: true, + }).href, + ).toBe('/game/tasks'); + expect(resolveStory(chapter, stayed, emptyStoryFacts()).progress.beatId).toBe( + 'stay', + ); + }); + + it('rejects an outcome that does not belong to the current beat', () => { + expect(() => + acknowledgePerformance( + chapter, + progress('watch'), + emptyStoryFacts(), + 'sample-play', + 'other', + ), + ).toThrow('演出结果不属于这一幕'); + }); +}); diff --git a/src/shared/story/resolve.ts b/src/shared/story/resolve.ts new file mode 100644 index 00000000..ef6ad3c9 --- /dev/null +++ b/src/shared/story/resolve.ts @@ -0,0 +1,123 @@ +import { + STORY_MARK_FACT_IDS, + type StoryBeat, + type StoryChapter, + type StoryFactId, + type StoryFacts, + type StoryProgress, + type StoryView, +} from './schema'; + +const markFacts = new Set(STORY_MARK_FACT_IDS); + +export interface StoryResolution { + progress: StoryProgress; + grants: string[]; +} + +function beatAt(chapter: StoryChapter, beatId: string): StoryBeat { + const beat = chapter.beats.find((entry) => entry.id === beatId); + if (!beat) throw new Error(`剧情幕不存在:${beatId}`); + return beat; +} + +function factReady( + fact: StoryFactId, + progress: StoryProgress, + facts: StoryFacts, +): boolean { + if (markFacts.has(fact)) return progress.marks.some((mark) => mark === fact); + return facts[fact]; +} + +function satisfied( + beat: StoryBeat, + progress: StoryProgress, + facts: StoryFacts, +): boolean { + if (beat.kind === 'performance') { + return progress.acks.includes(`${beat.script}:${beat.outcome}`); + } + if (beat.kind === 'practice') return factReady(beat.fact, progress, facts); + return false; +} + +export function resolveStory( + chapter: StoryChapter, + progress: StoryProgress, + facts: StoryFacts, +): StoryResolution { + if (progress.track !== chapter.track || progress.storyId !== chapter.id) { + throw new Error(`剧情进度不属于这一章:${progress.track}/${progress.storyId}`); + } + + let beatId = progress.beatId; + const grants = [...progress.grants]; + const issued: string[] = []; + const seen = new Set(); + + while (!seen.has(beatId)) { + seen.add(beatId); + const beat = beatAt(chapter, beatId); + if (!satisfied(beat, progress, facts)) break; + const index = chapter.beats.findIndex((entry) => entry.id === beatId); + const next = chapter.beats[index + 1]; + if (!next) break; + if (next.kind === 'practice' && next.grant && !grants.includes(next.grant)) { + grants.push(next.grant); + issued.push(next.grant); + } + beatId = next.id; + } + + return { + progress: { ...progress, beatId, grants }, + grants: issued, + }; +} + +export function acknowledgePerformance( + chapter: StoryChapter, + progress: StoryProgress, + facts: StoryFacts, + scriptId: string, + outcome: string, +): StoryResolution { + const key = `${scriptId}:${outcome}`; + if (progress.acks.includes(key)) { + return resolveStory(chapter, progress, facts); + } + const beat = beatAt(chapter, progress.beatId); + if (beat.kind !== 'performance' || beat.script !== scriptId) { + throw new Error('当前没有这场演出'); + } + if (beat.outcome !== outcome) throw new Error('演出结果不属于这一幕'); + return resolveStory( + chapter, + { ...progress, acks: [...progress.acks, key] }, + facts, + ); +} + +export function presentStory( + chapter: StoryChapter, + progress: StoryProgress, + facts: StoryFacts, +): StoryView { + const beat = beatAt(chapter, progress.beatId); + const shifted = + beat.kind === 'life' && beat.when && factReady(beat.when.fact, progress, facts) + ? { prompt: beat.when.prompt, href: beat.when.href } + : { prompt: beat.prompt, href: beat.href }; + return { + track: progress.track, + chapterId: chapter.id, + chapterTitle: chapter.title, + beatId: beat.id, + kind: beat.kind, + scene: beat.scene, + prompt: shifted.prompt, + href: shifted.href, + scriptId: beat.kind === 'performance' ? beat.script : null, + }; +} diff --git a/src/shared/story/schema.ts b/src/shared/story/schema.ts new file mode 100644 index 00000000..08f97d34 --- /dev/null +++ b/src/shared/story/schema.ts @@ -0,0 +1,141 @@ +import { z } from 'zod'; + +export const STORY_FACT_IDS = [ + 'starter_beast', + 'training_victory', + 'alchemy_crafted', + 'dungeon_settled', + 'sect_joined', + 'breakthrough_available', +] as const; + +export type StoryFactId = (typeof STORY_FACT_IDS)[number]; + +export const STORY_MARK_FACT_IDS = [ + 'training_victory', + 'alchemy_crafted', + 'dungeon_settled', +] as const satisfies readonly StoryFactId[]; + +export const STORY_TRACKS = ['main', 'encounter'] as const; + +export type StoryTrack = (typeof STORY_TRACKS)[number]; + +export const STORY_STATUSES = ['active', 'completed'] as const; + +export type StoryStatus = (typeof STORY_STATUSES)[number]; + +const factSchema = z.enum(STORY_FACT_IDS); + +const performanceBeatSchema = z + .object({ + id: z.string().trim().min(1).max(40), + kind: z.literal('performance'), + script: z.string().trim().min(1).max(80), + outcome: z.string().trim().min(1).max(40), + scene: z.string().trim().min(1).max(40), + prompt: z.string().max(80), + href: z.string().trim().min(1).max(120), + }) + .strict(); + +const practiceBeatSchema = z + .object({ + id: z.string().trim().min(1).max(40), + kind: z.literal('practice'), + fact: factSchema, + scene: z.string().trim().min(1).max(40), + prompt: z.string().trim().min(1).max(80), + href: z.string().trim().min(1).max(120), + grant: z.string().trim().min(1).max(40).optional(), + }) + .strict(); + +const lifeBeatSchema = z + .object({ + id: z.string().trim().min(1).max(40), + kind: z.literal('life'), + scene: z.string().trim().min(1).max(40), + prompt: z.string().max(80), + href: z.string().trim().min(1).max(120), + when: z + .object({ + fact: factSchema, + prompt: z.string().trim().min(1).max(80), + href: z.string().trim().min(1).max(120), + }) + .strict() + .optional(), + }) + .strict(); + +export const StoryBeatSchema = z.discriminatedUnion('kind', [ + performanceBeatSchema, + practiceBeatSchema, + lifeBeatSchema, +]); + +export const StoryChapterSchema = z + .object({ + id: z.string().trim().min(1).max(80), + track: z.enum(STORY_TRACKS), + title: z.string().trim().min(1).max(40), + beats: z.array(StoryBeatSchema).min(1).max(40), + }) + .strict() + .superRefine((chapter, context) => { + const ids = new Set(); + for (const beat of chapter.beats) { + if (ids.has(beat.id)) { + context.addIssue({ + code: 'custom', + message: `剧情幕重复:${beat.id}`, + }); + } + ids.add(beat.id); + } + }); + +export const StoryProgressSchema = z + .object({ + track: z.enum(STORY_TRACKS), + storyId: z.string().trim().min(1).max(80), + beatId: z.string().trim().min(1).max(80), + status: z.enum(STORY_STATUSES), + acks: z.array(z.string().trim().min(1).max(120)), + grants: z.array(z.string().trim().min(1).max(40)), + marks: z.array(z.enum(STORY_MARK_FACT_IDS)), + }) + .strict(); + +export const StoryViewSchema = z + .object({ + track: z.enum(STORY_TRACKS), + chapterId: z.string(), + chapterTitle: z.string(), + beatId: z.string(), + kind: z.enum(['performance', 'practice', 'life']), + scene: z.string(), + prompt: z.string(), + href: z.string(), + scriptId: z.string().nullable(), + }) + .strict(); + +export type StoryBeat = z.infer; +export type StoryChapter = z.infer; +export type StoryProgress = z.infer; +export type StoryView = z.infer; + +export type StoryFacts = Record; + +export function emptyStoryFacts(): StoryFacts { + return { + starter_beast: false, + training_victory: false, + alchemy_crafted: false, + dungeon_settled: false, + sect_joined: false, + breakthrough_available: false, + }; +}