From a5a2a2734d0134cdc76d903fb18acab8da3e5bc9 Mon Sep 17 00:00:00 2001 From: taojiacheng Date: Sun, 19 Jul 2026 00:05:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=9A=E5=AE=97=E9=97=A8?= =?UTF-8?q?=E8=A7=86=E5=9B=BE=E5=88=9D=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drizzle/0013_lively_living_tribunal.sql | 101 + drizzle/meta/0013_snapshot.json | 6270 +++++++++++++++++ drizzle/meta/_journal.json | 7 + public/assets/sect/lingxiao-map.webp | Bin 0 -> 299382 bytes .../components/feature/craft/AlchemyScene.tsx | 2118 ++++++ .../components/feature/craft/RefineScene.tsx | 434 ++ .../retreat}/BreakthroughConfirmModal.tsx | 2 +- .../feature/retreat}/RetreatResultModal.tsx | 8 +- .../feature/retreat}/RetreatView.tsx | 64 +- .../retreat}/retreatEfficiency.test.ts | 0 .../feature/retreat}/retreatEfficiency.ts | 0 .../feature/retreat}/retreatStream.test.ts | 0 .../feature/retreat}/retreatStream.ts | 0 .../feature/retreat}/useRetreatViewModel.tsx | 4 +- .../game-shell/gameNavigation.test.ts | 2 +- .../components/game-shell/gameNavigation.ts | 17 +- .../lib/router/gameShellRegistry.test.ts | 14 + src/react-app/lib/sect/sectClient.ts | 26 + src/react-app/router.tsx | 97 +- .../routes/game/craft/alchemy/route.tsx | 2096 +----- .../routes/game/craft/refine/route.tsx | 412 +- src/react-app/routes/game/retreat/route.tsx | 14 +- .../routes/game/sect/abilities/redirect.tsx | 5 + .../routes/game/sect/abilities/route.tsx | 25 +- .../routes/game/sect/affairs/route.tsx | 181 + .../routes/game/sect/alchemy/route.tsx | 12 + .../game/sect/archive/abilities/route.tsx | 5 + .../game/sect/archive/methods/route.tsx | 5 + .../routes/game/sect/archive/paths/route.tsx | 5 + .../routes/game/sect/archive/route.tsx | 42 + .../routes/game/sect/arena/route.tsx | 1 + src/react-app/routes/game/sect/cave/route.tsx | 10 + .../game/sect/components/CommissionsTab.tsx | 55 - .../routes/game/sect/components/SectScene.tsx | 147 + .../game/sect/cultivation-room/route.tsx | 16 + .../game/sect/enlightenment-cliff/route.tsx | 38 + src/react-app/routes/game/sect/gate/route.tsx | 9 + src/react-app/routes/game/sect/hall/route.tsx | 118 + .../routes/game/sect/herb-garden/route.tsx | 41 + .../routes/game/sect/industries/route.tsx | 63 + .../routes/game/sect/mapConfig.test.ts | 68 + src/react-app/routes/game/sect/mapConfig.ts | 30 + .../routes/game/sect/refinery/route.tsx | 12 + src/react-app/routes/game/sect/route.tsx | 168 +- .../routes/game/sect/spirit-vein/route.tsx | 39 + .../routes/game/sect/treasury/route.tsx | 26 + .../routes/game/sect/workshop/route.tsx | 5 + src/server/lib/drizzle/schema.ts | 183 + src/server/lib/jobs/internalCron.test.ts | 30 + src/server/lib/jobs/internalCron.ts | 29 + src/server/lib/jobs/internalCronScheduler.ts | 11 + .../sectOrganizationRepository.ts | 787 +++ src/server/lib/repositories/sectRepository.ts | 3 + .../lib/services/AlchemyFormulaService.ts | 14 +- src/server/lib/services/SectError.ts | 24 + .../lib/services/SectFacilityService.ts | 36 + .../services/SectOrganizationConfig.test.ts | 29 + .../lib/services/SectOrganizationConfig.ts | 324 + .../lib/services/SectOrganizationService.ts | 1104 +++ src/server/lib/services/SectService.ts | 48 +- src/server/lib/services/TaskService.ts | 11 +- src/server/lib/services/alchemyServiceV2.ts | 14 +- src/server/lib/services/creationServiceV2.ts | 32 +- src/server/lib/services/taskDefinitions.ts | 2 +- src/server/routes/api/craft.router.ts | 10 +- .../routes/api/cultivator.router.test.ts | 10 + src/server/routes/api/cultivator.router.ts | 8 +- src/server/routes/api/dungeon.router.ts | 5 - src/server/routes/api/rankings.router.ts | 6 - src/server/routes/api/sects.router.test.ts | 41 +- src/server/routes/api/sects.router.ts | 472 +- .../routes/internal/cron.router.test.ts | 12 + src/server/routes/internal/cron.router.ts | 5 + src/shared/contracts/sect.ts | 164 +- .../cultivation/CultivationEngine.test.ts | 11 + .../engine/cultivation/CultivationEngine.ts | 2 + src/shared/engine/sect/core/domain/index.ts | 1 + .../engine/sect/core/domain/organization.ts | 169 + src/shared/engine/sect/core/domain/state.ts | 4 + .../sect/core/tests/organization.test.ts | 29 + 80 files changed, 13570 insertions(+), 2872 deletions(-) create mode 100644 drizzle/0013_lively_living_tribunal.sql create mode 100644 drizzle/meta/0013_snapshot.json create mode 100644 public/assets/sect/lingxiao-map.webp create mode 100644 src/react-app/components/feature/craft/AlchemyScene.tsx create mode 100644 src/react-app/components/feature/craft/RefineScene.tsx rename src/react-app/{routes/game/retreat/components => components/feature/retreat}/BreakthroughConfirmModal.tsx (97%) rename src/react-app/{routes/game/retreat/components => components/feature/retreat}/RetreatResultModal.tsx (96%) rename src/react-app/{routes/game/retreat/components => components/feature/retreat}/RetreatView.tsx (90%) rename src/react-app/{routes/game/retreat/lib => components/feature/retreat}/retreatEfficiency.test.ts (100%) rename src/react-app/{routes/game/retreat/lib => components/feature/retreat}/retreatEfficiency.ts (100%) rename src/react-app/{routes/game/retreat/lib => components/feature/retreat}/retreatStream.test.ts (100%) rename src/react-app/{routes/game/retreat/lib => components/feature/retreat}/retreatStream.ts (100%) rename src/react-app/{routes/game/retreat/hooks => components/feature/retreat}/useRetreatViewModel.tsx (99%) create mode 100644 src/react-app/routes/game/sect/abilities/redirect.tsx create mode 100644 src/react-app/routes/game/sect/affairs/route.tsx create mode 100644 src/react-app/routes/game/sect/alchemy/route.tsx create mode 100644 src/react-app/routes/game/sect/archive/abilities/route.tsx create mode 100644 src/react-app/routes/game/sect/archive/methods/route.tsx create mode 100644 src/react-app/routes/game/sect/archive/paths/route.tsx create mode 100644 src/react-app/routes/game/sect/archive/route.tsx create mode 100644 src/react-app/routes/game/sect/arena/route.tsx create mode 100644 src/react-app/routes/game/sect/cave/route.tsx delete mode 100644 src/react-app/routes/game/sect/components/CommissionsTab.tsx create mode 100644 src/react-app/routes/game/sect/components/SectScene.tsx create mode 100644 src/react-app/routes/game/sect/cultivation-room/route.tsx create mode 100644 src/react-app/routes/game/sect/enlightenment-cliff/route.tsx create mode 100644 src/react-app/routes/game/sect/gate/route.tsx create mode 100644 src/react-app/routes/game/sect/hall/route.tsx create mode 100644 src/react-app/routes/game/sect/herb-garden/route.tsx create mode 100644 src/react-app/routes/game/sect/industries/route.tsx create mode 100644 src/react-app/routes/game/sect/mapConfig.test.ts create mode 100644 src/react-app/routes/game/sect/mapConfig.ts create mode 100644 src/react-app/routes/game/sect/refinery/route.tsx create mode 100644 src/react-app/routes/game/sect/spirit-vein/route.tsx create mode 100644 src/react-app/routes/game/sect/treasury/route.tsx create mode 100644 src/react-app/routes/game/sect/workshop/route.tsx create mode 100644 src/server/lib/repositories/sectOrganizationRepository.ts create mode 100644 src/server/lib/services/SectError.ts create mode 100644 src/server/lib/services/SectFacilityService.ts create mode 100644 src/server/lib/services/SectOrganizationConfig.test.ts create mode 100644 src/server/lib/services/SectOrganizationConfig.ts create mode 100644 src/server/lib/services/SectOrganizationService.ts create mode 100644 src/shared/engine/sect/core/domain/organization.ts create mode 100644 src/shared/engine/sect/core/tests/organization.test.ts diff --git a/drizzle/0013_lively_living_tribunal.sql b/drizzle/0013_lively_living_tribunal.sql new file mode 100644 index 00000000..043397e9 --- /dev/null +++ b/drizzle/0013_lively_living_tribunal.sql @@ -0,0 +1,101 @@ +CREATE TABLE "wanjiedaoyou_sect_construction_projects" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "sect_id" varchar(64) NOT NULL, + "facility_key" varchar(32) NOT NULL, + "target_level" integer NOT NULL, + "progress" integer DEFAULT 0 NOT NULL, + "target" integer NOT NULL, + "status" varchar(16) DEFAULT 'active' NOT NULL, + "started_week_key" varchar(10) NOT NULL, + "completed_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_contribution_ledger" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "membership_id" uuid NOT NULL, + "delta" integer NOT NULL, + "balance_after" integer NOT NULL, + "source" varchar(32) NOT NULL, + "reference_id" varchar(128), + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_donation_ledger" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "membership_id" uuid NOT NULL, + "project_id" uuid NOT NULL, + "date_key" varchar(10) NOT NULL, + "demand_id" varchar(64) NOT NULL, + "contribution" integer NOT NULL, + "construction_points" integer NOT NULL, + "item_snapshot" jsonb DEFAULT '{}'::jsonb NOT NULL, + "request_id" varchar(128), + "created_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_facilities" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "sect_id" varchar(64) NOT NULL, + "facility_key" varchar(32) NOT NULL, + "level" integer DEFAULT 1 NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_shop_purchases" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "membership_id" uuid NOT NULL, + "week_key" varchar(10) NOT NULL, + "item_id" varchar(64) NOT NULL, + "quantity" integer DEFAULT 1 NOT NULL, + "request_id" varchar(128), + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_stipend_claims" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "membership_id" uuid NOT NULL, + "week_key" varchar(10) NOT NULL, + "spirit_stones" integer NOT NULL, + "rewards" jsonb DEFAULT '[]'::jsonb NOT NULL, + "claimed_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "wanjiedaoyou_sect_task_records" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "membership_id" uuid NOT NULL, + "task_id" varchar(64) NOT NULL, + "kind" varchar(16) NOT NULL, + "period_key" varchar(16) NOT NULL, + "status" varchar(16) DEFAULT 'active' NOT NULL, + "progress" integer DEFAULT 0 NOT NULL, + "payload" jsonb DEFAULT '{}'::jsonb NOT NULL, + "completed_at" timestamp, + "claimed_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_memberships" ADD COLUMN "disciple_rank" varchar(16) DEFAULT 'registered' NOT NULL;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_memberships" ADD COLUMN "office" varchar(16) DEFAULT 'none' NOT NULL;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_memberships" ADD COLUMN "promoted_at" timestamp;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_contribution_ledger" ADD CONSTRAINT "wanjiedaoyou_sect_contribution_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk" FOREIGN KEY ("membership_id") REFERENCES "public"."wanjiedaoyou_sect_memberships"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_donation_ledger" ADD CONSTRAINT "wanjiedaoyou_sect_donation_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk" FOREIGN KEY ("membership_id") REFERENCES "public"."wanjiedaoyou_sect_memberships"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_donation_ledger" ADD CONSTRAINT "wanjiedaoyou_sect_donation_ledger_project_id_wanjiedaoyou_sect_construction_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."wanjiedaoyou_sect_construction_projects"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_shop_purchases" ADD CONSTRAINT "wanjiedaoyou_sect_shop_purchases_membership_id_wanjiedaoyou_sect_memberships_id_fk" FOREIGN KEY ("membership_id") REFERENCES "public"."wanjiedaoyou_sect_memberships"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_stipend_claims" ADD CONSTRAINT "wanjiedaoyou_sect_stipend_claims_membership_id_wanjiedaoyou_sect_memberships_id_fk" FOREIGN KEY ("membership_id") REFERENCES "public"."wanjiedaoyou_sect_memberships"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "wanjiedaoyou_sect_task_records" ADD CONSTRAINT "wanjiedaoyou_sect_task_records_membership_id_wanjiedaoyou_sect_memberships_id_fk" FOREIGN KEY ("membership_id") REFERENCES "public"."wanjiedaoyou_sect_memberships"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "sect_projects_active_sect_unique" ON "wanjiedaoyou_sect_construction_projects" USING btree ("sect_id") WHERE "wanjiedaoyou_sect_construction_projects"."status" = 'active';--> statement-breakpoint +CREATE INDEX "sect_projects_sect_created_idx" ON "wanjiedaoyou_sect_construction_projects" USING btree ("sect_id","created_at");--> statement-breakpoint +CREATE INDEX "sect_contribution_membership_created_idx" ON "wanjiedaoyou_sect_contribution_ledger" USING btree ("membership_id","created_at");--> statement-breakpoint +CREATE INDEX "sect_donation_member_date_idx" ON "wanjiedaoyou_sect_donation_ledger" USING btree ("membership_id","date_key");--> statement-breakpoint +CREATE UNIQUE INDEX "sect_donation_member_request_unique" ON "wanjiedaoyou_sect_donation_ledger" USING btree ("membership_id","request_id") WHERE "wanjiedaoyou_sect_donation_ledger"."request_id" is not null;--> statement-breakpoint +CREATE UNIQUE INDEX "sect_facilities_sect_key_unique" ON "wanjiedaoyou_sect_facilities" USING btree ("sect_id","facility_key");--> statement-breakpoint +CREATE INDEX "sect_shop_member_week_item_idx" ON "wanjiedaoyou_sect_shop_purchases" USING btree ("membership_id","week_key","item_id");--> statement-breakpoint +CREATE UNIQUE INDEX "sect_shop_member_request_unique" ON "wanjiedaoyou_sect_shop_purchases" USING btree ("membership_id","request_id") WHERE "wanjiedaoyou_sect_shop_purchases"."request_id" is not null;--> statement-breakpoint +CREATE UNIQUE INDEX "sect_stipend_member_week_unique" ON "wanjiedaoyou_sect_stipend_claims" USING btree ("membership_id","week_key");--> statement-breakpoint +CREATE UNIQUE INDEX "sect_task_membership_period_task_unique" ON "wanjiedaoyou_sect_task_records" USING btree ("membership_id","period_key","task_id");--> statement-breakpoint +CREATE INDEX "sect_task_membership_kind_period_idx" ON "wanjiedaoyou_sect_task_records" USING btree ("membership_id","kind","period_key"); \ No newline at end of file diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 00000000..0ab4e1bb --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,6270 @@ +{ + "id": "35e7f8ef-8888-446d-898b-274f18dd0f8c", + "prevId": "cb81ab94-a9c0-4c03-a7c8-506e4d91af21", + "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 + }, + "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": { + "name": "wanjiedaoyou_battle_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_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "challenge_type": { + "name": "challenge_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "opponent_cultivator_id": { + "name": "opponent_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "battle_result": { + "name": "battle_result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "battle_report": { + "name": "battle_report", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "battle_records_cultivator_created_idx": { + "name": "battle_records_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_opponent_created_idx": { + "name": "battle_records_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": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_battle_records_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_battle_records_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "opponent_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_battle_records_v2": { + "name": "wanjiedaoyou_battle_records_v2", + "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 + }, + "battle_type": { + "name": "battle_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'normal'" + }, + "opponent_cultivator_id": { + "name": "opponent_cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "engine_version": { + "name": "engine_version", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true, + "default": "'battle-v5'" + }, + "result_version": { + "name": "result_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "battle_result": { + "name": "battle_result", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "battle_report": { + "name": "battle_report", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "battle_records_v2_cultivator_created_idx": { + "name": "battle_records_v2_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_v2_opponent_created_idx": { + "name": "battle_records_v2_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_v2_user_created_idx": { + "name": "battle_records_v2_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": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_battle_records_v2_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_v2_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records_v2", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_battle_records_v2_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_battle_records_v2_opponent_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_battle_records_v2", + "tableTo": "wanjiedaoyou_cultivators", + "columnsFrom": [ + "opponent_cultivator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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_id": { + "name": "battle_record_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "battle_record_v2_id": { + "name": "battle_record_v2_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_id_wanjiedaoyou_battle_records_id_fk": { + "name": "wanjiedaoyou_bet_battles_battle_record_id_wanjiedaoyou_battle_records_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_battle_records", + "columnsFrom": [ + "battle_record_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "wanjiedaoyou_bet_battles_battle_record_v2_id_wanjiedaoyou_battle_records_v2_id_fk": { + "name": "wanjiedaoyou_bet_battles_battle_record_v2_id_wanjiedaoyou_battle_records_v2_id_fk", + "tableFrom": "wanjiedaoyou_bet_battles", + "tableTo": "wanjiedaoyou_battle_records_v2", + "columnsFrom": [ + "battle_record_v2_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_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" + }, + "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_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_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_state_versions": { + "name": "wanjiedaoyou_cultivator_state_versions", + "schema": "", + "columns": { + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "global_version": { + "name": "global_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "profile_version": { + "name": "profile_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "condition_version": { + "name": "condition_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_version": { + "name": "progress_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "currency_version": { + "name": "currency_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "loadout_version": { + "name": "loadout_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "mail_version": { + "name": "mail_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "tasks_version": { + "name": "tasks_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sect_version": { + "name": "sect_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wanjiedaoyou_cultivator_state_versions_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_cultivator_state_versions_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_cultivator_state_versions", + "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 + }, + "spirit": { + "name": "spirit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "wisdom": { + "name": "wisdom", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "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()" + }, + "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_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_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_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": { + "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_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_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_player_state_events": { + "name": "wanjiedaoyou_player_state_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "wanjiedaoyou_player_state_events_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "cultivator_id": { + "name": "cultivator_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "global_version": { + "name": "global_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "domain_version": { + "name": "domain_version", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "patch": { + "name": "patch", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "invalidates": { + "name": "invalidates", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "source": { + "name": "source", + "type": "varchar(96)", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "player_state_events_cultivator_version_idx": { + "name": "player_state_events_cultivator_version_idx", + "columns": [ + { + "expression": "cultivator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "global_version", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "player_state_events_user_idx": { + "name": "player_state_events_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "player_state_events_created_idx": { + "name": "player_state_events_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_player_state_events_cultivator_id_wanjiedaoyou_cultivators_id_fk": { + "name": "wanjiedaoyou_player_state_events_cultivator_id_wanjiedaoyou_cultivators_id_fk", + "tableFrom": "wanjiedaoyou_player_state_events", + "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": true + }, + "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_item_library_item_uidx": { + "name": "reputation_shop_item_library_item_uidx", + "columns": [ + { + "expression": "item_library_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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": true + }, + "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_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_ability_loadouts": { + "name": "wanjiedaoyou_sect_ability_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 + }, + "slot": { + "name": "slot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ability_id": { + "name": "ability_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_ability_membership_slot_unique": { + "name": "sect_ability_membership_slot_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_ability_membership_ability_unique": { + "name": "sect_ability_membership_ability_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ability_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_ability_loadouts_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_ability_loadouts_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_ability_loadouts", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_construction_projects": { + "name": "wanjiedaoyou_sect_construction_projects", + "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 + }, + "target_level": { + "name": "target_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "progress": { + "name": "progress", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "target": { + "name": "target", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "started_week_key": { + "name": "started_week_key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "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": true, + "default": "now()" + } + }, + "indexes": { + "sect_projects_active_sect_unique": { + "name": "sect_projects_active_sect_unique", + "columns": [ + { + "expression": "sect_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_sect_construction_projects\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_projects_sect_created_idx": { + "name": "sect_projects_sect_created_idx", + "columns": [ + { + "expression": "sect_id", + "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_sect_contribution_ledger": { + "name": "wanjiedaoyou_sect_contribution_ledger", + "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 + }, + "delta": { + "name": "delta", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "balance_after": { + "name": "balance_after", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_contribution_membership_created_idx": { + "name": "sect_contribution_membership_created_idx", + "columns": [ + { + "expression": "membership_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_sect_contribution_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_contribution_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_contribution_ledger", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_daily_commissions": { + "name": "wanjiedaoyou_sect_daily_commissions", + "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 + }, + "date_key": { + "name": "date_key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "completion_type": { + "name": "completion_type", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_commission_membership_date_unique": { + "name": "sect_commission_membership_date_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_daily_commissions_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_daily_commissions_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_daily_commissions", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wanjiedaoyou_sect_donation_ledger": { + "name": "wanjiedaoyou_sect_donation_ledger", + "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 + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "date_key": { + "name": "date_key", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "demand_id": { + "name": "demand_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "contribution": { + "name": "contribution", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "construction_points": { + "name": "construction_points", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "item_snapshot": { + "name": "item_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "request_id": { + "name": "request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_donation_member_date_idx": { + "name": "sect_donation_member_date_idx", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_donation_member_request_unique": { + "name": "sect_donation_member_request_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_sect_donation_ledger\".\"request_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "wanjiedaoyou_sect_donation_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_donation_ledger_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_donation_ledger", + "tableTo": "wanjiedaoyou_sect_memberships", + "columnsFrom": [ + "membership_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "wanjiedaoyou_sect_donation_ledger_project_id_wanjiedaoyou_sect_construction_projects_id_fk": { + "name": "wanjiedaoyou_sect_donation_ledger_project_id_wanjiedaoyou_sect_construction_projects_id_fk", + "tableFrom": "wanjiedaoyou_sect_donation_ledger", + "tableTo": "wanjiedaoyou_sect_construction_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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 + }, + "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 + }, + "active_path_id": { + "name": "active_path_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "contribution": { + "name": "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(64)", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "slot": { + "name": "slot", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "node_ids": { + "name": "node_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_meridian_membership_path_slot_unique": { + "name": "sect_meridian_membership_path_slot_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slot", + "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_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(64)", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "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_path_progress": { + "name": "wanjiedaoyou_sect_path_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 + }, + "path_id": { + "name": "path_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "unlocked_layer_ids": { + "name": "unlocked_layer_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "tactic_id": { + "name": "tactic_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "active_meridian_slot": { + "name": "active_meridian_slot", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sect_path_membership_path_unique": { + "name": "sect_path_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_path_progress_membership_id_wanjiedaoyou_sect_memberships_id_fk": { + "name": "wanjiedaoyou_sect_path_progress_membership_id_wanjiedaoyou_sect_memberships_id_fk", + "tableFrom": "wanjiedaoyou_sect_path_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_purchases": { + "name": "wanjiedaoyou_sect_shop_purchases", + "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 + }, + "item_id": { + "name": "item_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "request_id": { + "name": "request_id", + "type": "varchar(128)", + "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_shop_member_week_item_idx": { + "name": "sect_shop_member_week_item_idx", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "week_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sect_shop_member_request_unique": { + "name": "sect_shop_member_request_unique", + "columns": [ + { + "expression": "membership_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"wanjiedaoyou_sect_shop_purchases\".\"request_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "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": "cascade", + "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 + }, + "rewards": { + "name": "rewards", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "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": {} + } + }, + "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 + }, + "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_unique": { + "name": "sect_task_membership_period_task_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" + } + ], + "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_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_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 + } + }, + "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 14a8c28d..0da7fa9f 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1784168658491, "tag": "0012_wild_joystick", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1784387087618, + "tag": "0013_lively_living_tribunal", + "breakpoints": true } ] } \ No newline at end of file diff --git a/public/assets/sect/lingxiao-map.webp b/public/assets/sect/lingxiao-map.webp new file mode 100644 index 0000000000000000000000000000000000000000..ea714a5c458856c6483e1cafbd5ccf3b4e325bd0 GIT binary patch literal 299382 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�dB;XM%*1O4oVn~|Yd&e;EtQF@h(b?H-sF zK7nUHKylP*Qfh~GGI6$CXgoyd2Ht^~+m@orAqIms0ISy;b_~)K9-Pp!tbJaGoqQ=O z2irpJV%l#P%+g#wKzTKn)6B*yo8DP9FopGn82Y=^MNC}1C^L&~v@(fKrKUNnbZ61{ zuNXtuitN(>E=|<-Y99n;KyVROeBSD`q=HwV|V%iiIz`ZwmpBS<|V5Dha+f|3<8-Pnbi4 zaskc0^De$(Z_cTKou_G9bH0000HiZ6ly literal 0 HcmV?d00001 diff --git a/src/react-app/components/feature/craft/AlchemyScene.tsx b/src/react-app/components/feature/craft/AlchemyScene.tsx new file mode 100644 index 00000000..039bc0d9 --- /dev/null +++ b/src/react-app/components/feature/craft/AlchemyScene.tsx @@ -0,0 +1,2118 @@ +import { + PillAppearanceMark, + PillDetailGroups, + getPillFamilyLabel, + toPillDisplayModel, +} from '@app/components/feature/consumables'; +import { + MaterialSelectionModal, + SelectedMaterialsWithDose, +} from '@app/components/feature/creation'; +import { + GameSceneAsideSection, + GameSceneFrame, + GameSceneNote, + GameSceneSection, + GameSceneTabs, +} from '@app/components/game-shell'; +import { InkModal } from '@app/components/layout'; +import { useInkUI } from '@app/components/providers/InkUIProvider'; +import { + useQiActionConfirm, +} from '@app/components/feature/cultivator/useQiActionConfirm'; +import { + InkActionGroup, + InkBadge, + InkButton, + InkCard, + InkDialog, + InkIdentifyCelebration, + InkInput, + InkNotice, + ItemShowcaseModal, + type InkDialogState, +} from '@app/components/ui'; +import { + STARTER_ALCHEMY_PROMPT, +} from '@app/lib/alchemy/starterAlchemy'; +import { usePlayerStateView } from '@app/lib/player-state/selectors'; +import { useTaskList } from '@app/lib/hooks/useTaskList'; +import { usePlayerStateActions } from '@app/lib/player-state/store'; +import { findNextTutorialTask } from '@app/lib/tasks/taskClient'; +import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; +import { CREATION_INPUT_CONSTRAINTS } from '@shared/engine/creation-v2/config/CreationBalance'; +import { formatAlchemyPropertyVector } from '@shared/lib/alchemyProperties'; +import { cn } from '@shared/lib/cn'; +import { isPillConsumable } from '@shared/lib/consumables'; +import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; +import type { MaterialType, RealmType } from '@shared/types/constants'; +import type { + AlchemyBatchPreview, + AlchemyFormula, + AlchemyFormulaDiscoveryCandidate, + AlchemyMode, + FormulaAnalysisResult, + FormulaMaterialJudgment, + PillFamily, +} from '@shared/types/consumable'; +import { PILL_FAMILY_VALUES } from '@shared/types/consumable'; +import type { Consumable, Material } from '@shared/types/cultivator'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import { useNavigate } from 'react-router'; + +const ALLOWED_MATERIAL_TYPES = [ + 'herb', + 'ore', + 'monster', + 'tcdb', + 'aux', +] as const; +const CRAFT_TYPE = 'alchemy' as const; +const MAX_MATERIALS = CREATION_INPUT_CONSTRAINTS.maxMaterialKinds; +const MIN_DOSE = CREATION_INPUT_CONSTRAINTS.minQuantityPerMaterial; +const MAX_DOSE = CREATION_INPUT_CONSTRAINTS.maxQuantityPerMaterial; +const SPIRIT_STONES_LABEL = getGameConceptLabel('spirit_stones'); + +type PreviewValidation = { + valid: boolean; + blockingReason?: string; + warnings: string[]; +}; + +type AlchemyPreviewResponse = { + success: boolean; + data?: { + cost: { + spiritStones: number; + }; + canAfford: boolean; + validation: PreviewValidation; + batchPreview?: AlchemyBatchPreview; + }; + error?: string; +}; + +type PreviewState = { + key: string | null; + estimatedSpiritStones: number | null; + validation: PreviewValidation | null; + batchPreview: AlchemyBatchPreview | null; + canAfford: boolean; + previewError: string | null; +}; + +type FormulaProgress = { + previousLevel: number; + level: number; + exp: number; + gainedExp: number; + leveledUp: boolean; +}; + +type AlchemyCraftResponse = { + success: boolean; + data?: { + consumable: Consumable; + formulaDiscovery?: AlchemyFormulaDiscoveryCandidate; + formulaProgress?: FormulaProgress; + }; + error?: string; +}; + +type FormulaListResponse = { + success: boolean; + data?: { + formulas: AlchemyFormula[]; + pagination: FormulaListPagination; + }; + error?: string; +}; + +type FormulaListPagination = { + page: number; + pageSize: number; + total: number; + totalPages: number; + hasPreviousPage: boolean; + hasNextPage: boolean; +}; + +type DiscoveryConfirmResponse = { + success: boolean; + data?: { + saved: boolean; + formula?: AlchemyFormula; + }; + error?: string; +}; + +type FormulaDeleteResponse = { + success: boolean; + message?: string; + error?: string; +}; + +type FormulaAnalyzeResponse = { + success: boolean; + data?: FormulaAnalysisResult; + error?: string; + remainingSeconds?: number; +}; + +const DEFAULT_PREVIEW_STATE: PreviewState = { + key: null, + estimatedSpiritStones: null, + validation: null, + batchPreview: null, + canAfford: true, + previewError: null, +}; + +const DEFAULT_FORMULA_PAGINATION: FormulaListPagination = { + page: 1, + pageSize: 5, + total: 0, + totalPages: 1, + hasPreviousPage: false, + hasNextPage: false, +}; + +function formatFormulaTags(formula: AlchemyFormula): string { + return formatAlchemyPropertyVector(formula.pattern.targetPropertyVector); +} + +function getFormulaMasteryRequiredExp(formula: AlchemyFormula): number { + return 5 * (formula.mastery.level + 1); +} + +function getFormulaFitBandLabel( + fitBand: FormulaAnalysisResult['fitBand'], +): string { + switch (fitBand) { + case 'aligned': + return '契合'; + case 'degraded': + return '勉强'; + case 'poor': + return '偏路'; + } +} + +function getFormulaFitBandTone( + fitBand: FormulaAnalysisResult['fitBand'], +): 'accent' | 'warning' | 'danger' { + switch (fitBand) { + case 'aligned': + return 'accent'; + case 'degraded': + return 'warning'; + case 'poor': + return 'danger'; + } +} + +function getFormulaAnalysisNarrative( + fitBand: FormulaAnalysisResult['fitBand'], +): string { + switch (fitBand) { + case 'aligned': + return '炉中药脉已顺着丹方主路收束,此刻开炉,最易成丹。'; + case 'degraded': + return '这一炉尚能循方而行,只是药力已有散逸,成丹后难免折损几分。'; + case 'poor': + return '这一炉药路偏离丹方甚远,仍可强行收丹,但药效和品相都会明显受损。'; + } +} + +function getFormulaFitBandEffectText( + fitBand: FormulaAnalysisResult['fitBand'], +): string { + switch (fitBand) { + case 'aligned': + return '药效与品相概率按丹方正常发挥,契合越高熟练增长越快。'; + case 'degraded': + return '药效会有折损,品相更难上行,熟练增长较少。'; + case 'poor': + return '药效削减明显,品相大多偏下,且基本不会增长熟练。'; + } +} + +function getBatchTierLabel( + tier: NonNullable['compoundTier'], +): string { + switch (tier) { + case 'single': + return '单材'; + case 'balanced': + return '均衡'; + case 'synergy': + return '协同'; + case 'conflict': + return '冲突'; + } +} + +export function AlchemyGuideModal({ + isOpen, + onClose, +}: { + isOpen: boolean; + onClose: () => void; +}) { + return ( + +

+
+
+ 即兴炼丹 +
+
+

+ 先挑灵材,再写丹意。你写的是想成什么丹,炉中灵材决定这股药力能往哪条路走:疗伤、回元、积修、启悟、破境、清心、护脉、延寿、炼体、洗髓,都会从这里分出路数。 +

+

+ 丹意写得越清楚,炉火越容易顺着你的意思走;灵材本性若不相合,也会把丹势带偏。想炼筑基丹,可投些温稳灵草或辅材,再写“冲关蓄势、辅助筑基、破境凝神”。 +

+

+ 想推动肉身炼体时,丹意要写清皮肤、筋骨、脏腑、气血或元神等方向。成丹名称仍随材料与炉意生成,不再固定叫某一种炼体丹。 +

+

+ 偶有一炉火候极顺、药路分明,出丹时便可能悟出丹方。留下丹方后,下次就能按方复炼。 +

+
+
+ +
+
+ 丹方炼制 +
+
+

+ 丹方记的是成丹路数,不是死记哪几味药。先选丹方,再把你手头相近的灵材投入炉中;若炉位、品阶或灵石不够,炉前会直接拦下。 +

+

+ 投料后先“推演药路”。这一步会看每味材料是能当主材、只能凑用,还是会拖偏丹方。 +

+

+ 药路若见“契合”,便可安心开炉;若只是“勉强”或“偏路”,也能强行成丹,只是药力、品相和熟练收益都会受影响。 +

+
+
+ +
+
+ 炉火提醒 +
+
+

+ 一炉里药味越多、方向越杂,火势越难稳。炉势不稳时即便成丹,疗伤、修为、感悟、炼体、延寿等药力也会打折。 +

+

+ 猛药常带丹毒,燥烈材料用得多,服后更要记得调息。解毒一类丹药能化浊,但这一炉的主要药力也会分去一部分。 +

+

+ 修为、启悟、破境、清心、护脉、延寿、炼体、洗髓都不是一口吞尽的便宜事。同一炉最好只求一条主路,丹意写得太散,反而什么都留不深。 +

+

+ 破境丹服下后,可能留下破境凝神、清心或护脉之效。破境前看的是你身上有没有这份准备,不问这枚丹是不是亲手炼成。 +

+

+ 肉身进阶另走炼体体系:看丹药是否为炼体类、药性是否对应目标五轨、丹药质量是否达标,不按丹药名称判断。 +

+
+
+
+ + ); +} + +export function FormulaNarrativeBlock({ + formula, + showMasteryExp = false, +}: { + formula: AlchemyFormula; + showMasteryExp?: boolean; +}) { + return ( +
+
{formula.description}
+
药路取向:{formatFormulaTags(formula)}
+
+ 需 {formula.pattern.slotCount} 味灵材 + {formula.pattern.minQuality + ? `,至少 ${formula.pattern.minQuality}` + : ''} + {showMasteryExp ? `,熟练进度 ${formula.mastery.exp}` : ''} +
+
+ ); +} + +export function AlchemyFormulaSummaryCard({ + formula, +}: { + formula: AlchemyFormula; +}) { + return ( + +
+
+ {formula.name} + + {getPillFamilyLabel(formula.family)} + + + {`熟练 Lv.${formula.mastery.level}`} + +
+ +
+
+ ); +} + +export function AlchemyFormulaAnalysisModal({ + analysis, + cooldownRemainingSeconds, + isOpen, + isCrafting, + onClose, + onCraft, +}: { + analysis: FormulaAnalysisResult | null; + cooldownRemainingSeconds: number; + isOpen: boolean; + isCrafting: boolean; + onClose: () => void; + onCraft: () => void; +}) { + if (!analysis) { + return null; + } + + return ( + + + 稍后开炉 + + + {isCrafting ? '丹火炼中……' : '确认开炉'} + + + } + > +
+
+ + {getFormulaFitBandLabel(analysis.fitBand)} + + + 合方程度 {Math.round(analysis.fitScore * 100)}% + +
+ {analysis.batchProfile ? ( +
+
+
预计成丹
+
+ {analysis.batchProfile.yieldQuantity} 枚 +
+
+
+
配伍
+
+ {getBatchTierLabel(analysis.batchProfile.compoundTier)} +
+
+ {analysis.batchProfile.roleSummary} +
+
+
+ ) : null} +
+

{getFormulaAnalysisNarrative(analysis.fitBand)}

+

{getFormulaFitBandEffectText(analysis.fitBand)}

+ {cooldownRemainingSeconds > 0 ? ( +

+ 本次推演已留炉路,可直接开炉;{cooldownRemainingSeconds}{' '} + 秒后可重新推演药路。 +

+ ) : null} +
+ {analysis.materialJudgments.length > 0 ? ( +
+ {analysis.materialJudgments.map((judgment) => ( +
+ + {judgment.verdict === 'core' + ? '主材' + : judgment.verdict === 'usable' + ? '可用' + : '偏路'} + + {judgment.materialName} + {judgment.reason} +
+ ))} +
+ ) : null} + {analysis.warnings.length > 0 ? ( +
+ {analysis.warnings.map((warning) => ( + + {warning} + + ))} +
+ ) : null} +
+
+ ); +} + +export function AlchemyFormulaListItem({ + formula, + isActive, + isDeleting, + onSelect, + onDelete, +}: { + formula: AlchemyFormula; + isActive: boolean; + isDeleting: boolean; + onSelect: () => void; + onDelete: () => void; +}) { + const requiredMasteryExp = getFormulaMasteryRequiredExp(formula); + + return ( +
{ + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + onSelect(); + } + }} + className={cn( + 'w-full border px-3 py-3 text-left transition-colors', + isActive + ? 'border-crimson bg-crimson/5' + : 'border-ink/10 hover:border-ink/30', + )} + > +
+
+
+ {formula.name} + + {getPillFamilyLabel(formula.family)} + +
+
+ {`熟练 Lv.${formula.mastery.level}`} + {`进度 ${formula.mastery.exp}/${requiredMasteryExp}`} + {`需 ${formula.pattern.slotCount} 味灵材`} + {formula.pattern.minQuality ? ( + {`至少 ${formula.pattern.minQuality}`} + ) : null} +
+
+

{formula.description}

+

药路取向:{formatFormulaTags(formula)}

+
+
+
event.stopPropagation()} + onKeyDown={(event) => event.stopPropagation()} + > + + {isDeleting ? '删除中……' : '删除'} + +
+
+
+ ); +} + +export function AlchemyFormulaSelectionModal({ + isOpen, + onClose, + formulas, + selectedFormulaId, + isLoading, + error, + search, + familyFilter, + pagination, + isDeleting, + isSubmitting, + onSearchChange, + onFamilyFilterChange, + onPageChange, + onSelectFormula, + onDeleteFormula, +}: { + isOpen: boolean; + onClose: () => void; + formulas: AlchemyFormula[]; + selectedFormulaId: string | null; + isLoading: boolean; + error: string | null; + search: string; + familyFilter: PillFamily | 'all'; + pagination: FormulaListPagination; + isDeleting: boolean; + isSubmitting: boolean; + onSearchChange: (value: string) => void; + onFamilyFilterChange: (value: PillFamily | 'all') => void; + onPageChange: (page: number) => void; + onSelectFormula: (formula: AlchemyFormula) => void; + onDeleteFormula: (formula: AlchemyFormula) => void; +}) { + return ( + +
+ 共 {pagination.total} 份丹方,第 {pagination.page} /{' '} + {pagination.totalPages} 页 +
+ + onPageChange(pagination.page - 1)} + disabled={ + isSubmitting || isLoading || !pagination.hasPreviousPage + } + > + 上一页 + + onPageChange(pagination.page + 1)} + disabled={isSubmitting || isLoading || !pagination.hasNextPage} + > + 下一页 + + + 完成选择 + + + + } + > +
+
+ + +
+ + {error ? {error} : null} + {isLoading ? 正在整理你的丹方笔录…… : null} + {!isLoading && formulas.length === 0 ? ( + + {search || familyFilter !== 'all' + ? '没有符合筛选条件的丹方。' + : '你尚未悟得丹方。先在“即兴炼丹”中炼出稳固成丹,再尝试留方。'} + + ) : null} + +
+ {formulas.map((formula) => ( + onSelectFormula(formula)} + onDelete={() => onDeleteFormula(formula)} + /> + ))} +
+
+
+ ); +} + +export function AlchemyResultModal({ + consumable, + formulaProgress, + isOpen, + onClose, + viewerRealm, +}: { + consumable: Consumable | null; + formulaProgress: FormulaProgress | null; + isOpen: boolean; + onClose: () => void; + viewerRealm?: RealmType; +}) { + if (!consumable || !isPillConsumable(consumable)) { + return null; + } + + const model = toPillDisplayModel(consumable, { realm: viewerRealm }); + const meta = consumable.spec.alchemyMeta; + + return ( + + ) : undefined + } + badges={[ + consumable.quality ? ( + + {consumable.type} + + ) : undefined, + + {getPillFamilyLabel(consumable.spec.family)} + , + meta.source === 'formula' ? ( + + 丹方炼制 + + ) : undefined, + ].filter(Boolean)} + metaSection={ +
+
+ 本炉成丹 + + {consumable.quantity} 枚 + +
+ {meta.batch ? ( +
+ 配伍 + + {getBatchTierLabel(meta.batch.compoundTier)} ·{' '} + {meta.batch.roleSummary} + +
+ ) : null} + + {formulaProgress && ( +
+
丹方熟练
+
+
本次熟练 +{formulaProgress.gainedExp}
+
+ 当前等级 Lv.{formulaProgress.level},进度{' '} + {formulaProgress.exp} +
+ {formulaProgress.leveledUp && ( +
+ 丹方熟练提升:Lv.{formulaProgress.previousLevel} → Lv. + {formulaProgress.level} +
+ )} +
+
+ )} +
+ } + description={consumable.description} + descriptionTitle="丹成评述" + /> + ); +} + +export function AlchemyFormulaDiscoveryModal({ + formulaDiscovery, + isHandlingDiscovery, + isOpen, + onAcceptDiscovery, + onRejectDiscovery, +}: { + formulaDiscovery: AlchemyFormulaDiscoveryCandidate | null; + isHandlingDiscovery: boolean; + isOpen: boolean; + onAcceptDiscovery: () => void; + onRejectDiscovery: () => void; +}) { + if (!formulaDiscovery) { + return null; + } + + return ( + undefined} + icon="📜" + name={formulaDiscovery.name} + badges={[ + + 新悟丹方 + , + + {getPillFamilyLabel(formulaDiscovery.family)} + , + ]} + metaSection={ +
+ +
+
{formulaDiscovery.discoveryRemark}
+
+ {formulaDiscovery.patternSummary} +
+
+
+
+ } + description={formulaDiscovery.description} + descriptionTitle="留方记述" + footer={ + + + 暂不保存 + + + {isHandlingDiscovery ? '留方中……' : '保存丹方'} + + + } + /> + ); +} + +export type AlchemySceneProps = { + sectContext?: { + facilityLevel: number; + discountPercent: number; + }; +}; + +export function AlchemyScene({ sectContext }: AlchemySceneProps) { + const { cultivator, note, isLoading } = usePlayerStateView(); + const { mutate } = usePlayerStateActions(); + const cultivatorId = cultivator?.id ?? null; + const { tasks } = useTaskList(cultivatorId ?? undefined); + const [activeMode, setActiveMode] = useState('improvised'); + const [selectedFormulaId, setSelectedFormulaId] = useState( + null, + ); + const [selectedMaterialIds, setSelectedMaterialIds] = useState([]); + const [selectedMaterialMap, setSelectedMaterialMap] = useState< + Record + >({}); + const [doseMap, setDoseMap] = useState>({}); + const [userPrompt, setUserPrompt] = useState(''); + const [status, setStatus] = useState(''); + const [isSubmitting, setSubmitting] = useState(false); + const [createdConsumable, setCreatedConsumable] = useState( + null, + ); + const [formulaDiscovery, setFormulaDiscovery] = + useState(null); + const [formulaProgress, setFormulaProgress] = + useState(null); + const [isResultModalOpen, setIsResultModalOpen] = useState(false); + const [isDiscoveryModalOpen, setIsDiscoveryModalOpen] = useState(false); + const [isHandlingDiscovery, setIsHandlingDiscovery] = useState(false); + const [isDeletingFormula, setIsDeletingFormula] = useState(false); + const [isGuideModalOpen, setIsGuideModalOpen] = useState(false); + const [isMaterialModalOpen, setIsMaterialModalOpen] = useState(false); + const [isFormulaSelectionModalOpen, setIsFormulaSelectionModalOpen] = + useState(false); + const [dialog, setDialog] = useState(null); + const [celebrationTick, setCelebrationTick] = useState(0); + const [materialsRefreshKey, setMaterialsRefreshKey] = useState(0); + const [previewState, setPreviewState] = useState( + DEFAULT_PREVIEW_STATE, + ); + const [formulaAnalysis, setFormulaAnalysis] = + useState(null); + const [isFormulaAnalysisModalOpen, setIsFormulaAnalysisModalOpen] = + useState(false); + const [isAnalyzingFormula, setIsAnalyzingFormula] = useState(false); + const [formulaAnalysisError, setFormulaAnalysisError] = useState< + string | null + >(null); + const [analysisCooldownRemaining, setAnalysisCooldownRemaining] = + useState(0); + const [analysisExpiresAfterMs, setAnalysisExpiresAfterMs] = useState( + null, + ); + const [formulas, setFormulas] = useState([]); + const [selectedFormulaSnapshot, setSelectedFormulaSnapshot] = + useState(null); + const [formulaSearch, setFormulaSearch] = useState(''); + const [formulaFamilyFilter, setFormulaFamilyFilter] = useState< + PillFamily | 'all' + >('all'); + const [formulaPage, setFormulaPage] = useState(1); + const [formulaPagination, setFormulaPagination] = + useState(DEFAULT_FORMULA_PAGINATION); + const [formulasError, setFormulasError] = useState(null); + const [isLoadingFormulas, setIsLoadingFormulas] = useState(false); + const analyzedFormulaSelectionKeyRef = useRef(null); + const { pushToast } = useInkUI(); + const { openQiActionConfirm } = useQiActionConfirm(); + const navigate = useNavigate(); + const selectedFormula = useMemo( + () => + selectedFormulaSnapshot?.id === selectedFormulaId + ? selectedFormulaSnapshot + : (formulas.find((formula) => formula.id === selectedFormulaId) ?? null), + [formulas, selectedFormulaId, selectedFormulaSnapshot], + ); + const nextTutorialTask = useMemo(() => findNextTutorialTask(tasks), [tasks]); + const isStarterAlchemyTask = + nextTutorialTask?.definitionId === 'tutorial_first_alchemy'; + const formulaJudgmentMap = useMemo( + () => + Object.fromEntries( + (formulaAnalysis?.materialJudgments ?? []).map((judgment) => [ + judgment.materialId, + judgment, + ]), + ) as Record, + [formulaAnalysis], + ); + const currentFormulaSelectionKey = useMemo(() => { + if (activeMode !== 'formula' || !selectedFormulaId) { + return null; + } + + return JSON.stringify({ + formulaId: selectedFormulaId, + materials: selectedMaterialIds.map((id) => ({ + id, + dose: doseMap[id] ?? MIN_DOSE, + })), + }); + }, [activeMode, doseMap, selectedFormulaId, selectedMaterialIds]); + + useEffect(() => { + if (analysisCooldownRemaining <= 0) { + return; + } + + const timer = window.setInterval(() => { + setAnalysisCooldownRemaining((current) => Math.max(0, current - 1)); + }, 1000); + + return () => window.clearInterval(timer); + }, [analysisCooldownRemaining]); + + useEffect(() => { + if (!formulaAnalysis || !analysisExpiresAfterMs) { + return; + } + + const timer = window.setTimeout(() => { + setFormulaAnalysis(null); + setIsFormulaAnalysisModalOpen(false); + setAnalysisExpiresAfterMs(null); + setFormulaAnalysisError('上次推演已散去,请重新推演药路。'); + }, analysisExpiresAfterMs); + + return () => window.clearTimeout(timer); + }, [analysisExpiresAfterMs, formulaAnalysis]); + + const clearFormulaAnalysis = (options?: { keepError?: boolean }) => { + setFormulaAnalysis(null); + setIsFormulaAnalysisModalOpen(false); + setAnalysisExpiresAfterMs(null); + analyzedFormulaSelectionKeyRef.current = null; + if (!options?.keepError) { + setFormulaAnalysisError(null); + } + }; + + useEffect(() => { + if ( + analyzedFormulaSelectionKeyRef.current && + analyzedFormulaSelectionKeyRef.current !== currentFormulaSelectionKey + ) { + analyzedFormulaSelectionKeyRef.current = null; + setFormulaAnalysis(null); + setIsFormulaAnalysisModalOpen(false); + setAnalysisExpiresAfterMs(null); + setFormulaAnalysisError(null); + } + }, [currentFormulaSelectionKey]); + + const loadFormulas = async (options?: { + page?: number; + }) => { + if (!cultivatorId) { + return; + } + + try { + setIsLoadingFormulas(true); + const nextPage = options?.page ?? formulaPage; + const params = new URLSearchParams({ + page: String(nextPage), + pageSize: '5', + }); + const keyword = formulaSearch.trim(); + if (keyword) { + params.set('search', keyword); + } + if (formulaFamilyFilter !== 'all') { + params.set('family', formulaFamilyFilter); + } + const response = await fetch(`/api/alchemy/formulas?${params.toString()}`); + const result: FormulaListResponse = await response.json(); + + if (!response.ok || !result.success || !result.data) { + throw new Error(result.error || '丹方列表读取失败'); + } + + const nextFormulas = result.data.formulas; + + setFormulas(nextFormulas); + setFormulaPagination(result.data.pagination); + setFormulaPage(result.data.pagination.page); + setFormulasError(null); + } catch (error) { + setFormulasError( + error instanceof Error + ? error.message + : '丹方列表读取失败,请稍后再试。', + ); + } finally { + setIsLoadingFormulas(false); + } + }; + + useEffect(() => { + if (!cultivatorId || !isFormulaSelectionModalOpen) { + return; + } + + let cancelled = false; + + const loadInitialFormulas = async () => { + try { + setIsLoadingFormulas(true); + const params = new URLSearchParams({ + page: String(formulaPage), + pageSize: '5', + }); + const keyword = formulaSearch.trim(); + if (keyword) { + params.set('search', keyword); + } + if (formulaFamilyFilter !== 'all') { + params.set('family', formulaFamilyFilter); + } + const response = await fetch(`/api/alchemy/formulas?${params.toString()}`); + const result: FormulaListResponse = await response.json(); + + if (cancelled) return; + + if (!response.ok || !result.success || !result.data) { + throw new Error(result.error || '丹方列表读取失败'); + } + + const nextFormulas = result.data.formulas; + + setFormulas(nextFormulas); + setFormulaPagination(result.data.pagination); + setFormulaPage(result.data.pagination.page); + setFormulasError(null); + } catch (error) { + if (cancelled) return; + setFormulasError( + error instanceof Error + ? error.message + : '丹方列表读取失败,请稍后再试。', + ); + } finally { + if (!cancelled) { + setIsLoadingFormulas(false); + } + } + }; + + void loadInitialFormulas(); + + return () => { + cancelled = true; + }; + }, [ + cultivatorId, + formulaFamilyFilter, + formulaPage, + formulaSearch, + isFormulaSelectionModalOpen, + ]); + + const previewRequest = useMemo(() => { + if (selectedMaterialIds.length === 0) { + return null; + } + if (activeMode === 'formula' && !selectedFormulaId) { + return null; + } + + const params = new URLSearchParams({ + craftType: CRAFT_TYPE, + alchemyMode: activeMode, + materialIds: selectedMaterialIds.join(','), + }); + const materialQuantities = Object.fromEntries( + selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), + ); + params.set('materialQuantities', JSON.stringify(materialQuantities)); + if (activeMode === 'formula' && selectedFormulaId) { + params.set('formulaId', selectedFormulaId); + } + + return { + key: JSON.stringify({ + activeMode, + selectedFormulaId, + materialIds: selectedMaterialIds, + materialQuantities, + }), + url: `/api/craft?${params.toString()}`, + }; + }, [activeMode, doseMap, selectedFormulaId, selectedMaterialIds]); + + useEffect(() => { + if (!previewRequest) { + return; + } + + let cancelled = false; + + const loadPreview = async () => { + try { + const response = await fetch(previewRequest.url); + const result: AlchemyPreviewResponse = await response.json(); + + if (cancelled) return; + + if (!response.ok || !result.success || !result.data) { + throw new Error(result.error || '炼丹预估失败'); + } + + setPreviewState({ + key: previewRequest.key, + estimatedSpiritStones: result.data.cost.spiritStones, + validation: result.data.validation, + batchPreview: result.data.batchPreview ?? null, + canAfford: result.data.canAfford, + previewError: null, + }); + } catch (error) { + if (cancelled) return; + setPreviewState({ + key: previewRequest.key, + estimatedSpiritStones: null, + validation: null, + batchPreview: null, + canAfford: true, + previewError: + error instanceof Error + ? error.message + : '炼丹预估失败,请稍后再试。', + }); + } + }; + + void loadPreview(); + + return () => { + cancelled = true; + }; + }, [previewRequest]); + + const resetWorkbench = () => { + setStatus(''); + setCreatedConsumable(null); + setFormulaDiscovery(null); + setFormulaProgress(null); + setIsResultModalOpen(false); + setIsDiscoveryModalOpen(false); + setSelectedMaterialIds([]); + setSelectedMaterialMap({}); + setDoseMap({}); + setUserPrompt(''); + setPreviewState(DEFAULT_PREVIEW_STATE); + clearFormulaAnalysis(); + setIsMaterialModalOpen(false); + }; + + const handleModeChange = (value: string) => { + const nextMode = value as AlchemyMode; + if (nextMode === activeMode) { + return; + } + + setActiveMode(nextMode); + resetWorkbench(); + }; + + const toggleMaterial = (id: string, material?: Material) => { + clearFormulaAnalysis(); + setSelectedMaterialIds((prev) => { + if (prev.includes(id)) { + setSelectedMaterialMap((map) => { + const next = { ...map }; + delete next[id]; + return next; + }); + setDoseMap((map) => { + const next = { ...map }; + delete next[id]; + return next; + }); + return prev.filter((mid) => mid !== id); + } + if (prev.length >= MAX_MATERIALS) { + pushToast({ + message: `丹炉承载有限,最多投入 ${MAX_MATERIALS} 种灵材`, + tone: 'warning', + }); + return prev; + } + if (material) { + setSelectedMaterialMap((map) => ({ ...map, [id]: material })); + setDoseMap((map) => ({ ...map, [id]: MIN_DOSE })); + } + return [...prev, id]; + }); + }; + + const handleDoseChange = (id: string, dose: number) => { + const material = selectedMaterialMap[id]; + if (!material) return; + clearFormulaAnalysis(); + const stock = material.quantity ?? 0; + const clamped = Math.min( + Math.min(MAX_DOSE, Math.max(stock, MIN_DOSE)), + Math.max(MIN_DOSE, Math.floor(dose)), + ); + setDoseMap((prev) => ({ ...prev, [id]: clamped })); + }; + + const resetAll = () => { + resetWorkbench(); + }; + + const submitPayload = useMemo( + () => ({ + materialIds: selectedMaterialIds, + craftType: CRAFT_TYPE, + alchemyMode: activeMode, + formulaId: activeMode === 'formula' ? selectedFormulaId : undefined, + analysisId: + activeMode === 'formula' ? formulaAnalysis?.analysisId : undefined, + materialQuantities: Object.fromEntries( + selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), + ), + userPrompt: activeMode === 'improvised' ? userPrompt.trim() : undefined, + }), + [ + activeMode, + doseMap, + formulaAnalysis?.analysisId, + selectedFormulaId, + selectedMaterialIds, + userPrompt, + ], + ); + + const hasFreshPreview = previewState.key === previewRequest?.key; + const estimatedSpiritStones = hasFreshPreview + ? previewState.estimatedSpiritStones + : null; + const batchPreview = hasFreshPreview ? previewState.batchPreview : null; + const validation = hasFreshPreview ? previewState.validation : null; + const canAfford = hasFreshPreview ? previewState.canAfford : true; + const previewError = hasFreshPreview ? previewState.previewError : null; + const displayValidation = validation; + const displayCanAfford = canAfford; + const isFormulaMode = activeMode === 'formula'; + const displayBatchPreview = isFormulaMode ? batchPreview : null; + const displayPreviewWarnings = + isFormulaMode ? (displayValidation?.warnings ?? []) : []; + const qiCost = isFormulaMode + ? QI_ACTION_COSTS.alchemy_formula + : QI_ACTION_COSTS.alchemy_improvised; + const hasCraftableFormulaAnalysis = + !!formulaAnalysis?.analysisId; + const canAnalyzeFormula = + !isSubmitting && + !isAnalyzingFormula && + !!selectedFormulaId && + selectedMaterialIds.length > 0 && + hasFreshPreview && + estimatedSpiritStones !== null && + !previewError && + displayCanAfford && + displayValidation?.valid !== false && + analysisCooldownRemaining <= 0; + const canCraftFormula = + !isSubmitting && + !!selectedFormulaId && + hasCraftableFormulaAnalysis && + !previewError && + estimatedSpiritStones !== null && + displayCanAfford && + displayValidation?.valid !== false; + const canChooseMaterials = !isSubmitting && (!isFormulaMode || !!selectedFormulaId); + const formulaPrimaryButtonLabel = isSubmitting + ? '丹火炼中……' + : isAnalyzingFormula + ? '推演中……' + : !formulaAnalysis?.analysisId + ? analysisCooldownRemaining > 0 + ? `${analysisCooldownRemaining} 秒后可再推演` + : '推演药路' + : formulaAnalysis.fitBand === 'poor' + ? '强行开炉' + : '依方成丹'; + + const handleAnalyzeFormula = async () => { + if (!cultivator) { + pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); + return; + } + if (!selectedFormulaId) { + pushToast({ message: '请先选定丹方。', tone: 'warning' }); + return; + } + if (selectedMaterialIds.length === 0) { + pushToast({ message: '请先投入灵材。', tone: 'warning' }); + return; + } + if (!hasFreshPreview || estimatedSpiritStones === null) { + pushToast({ message: '炉前验材尚未完成。', tone: 'warning' }); + return; + } + if (previewError || displayValidation?.valid === false || !displayCanAfford) { + pushToast({ message: '请先让这一炉通过验材。', tone: 'warning' }); + return; + } + if (analysisCooldownRemaining > 0) { + pushToast({ + message: `炉意未散,请 ${analysisCooldownRemaining} 秒后再推演药路。`, + tone: 'warning', + }); + return; + } + + setIsAnalyzingFormula(true); + setFormulaAnalysisError(null); + + try { + const response = await fetch( + `/api/alchemy/formulas/${selectedFormulaId}/analyze`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + materialIds: selectedMaterialIds, + materialQuantities: Object.fromEntries( + selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), + ), + }), + }, + ); + const result: FormulaAnalyzeResponse = await response.json(); + + if (!response.ok || !result.success || !result.data) { + if (typeof result.remainingSeconds === 'number') { + setAnalysisCooldownRemaining(result.remainingSeconds); + } + throw new Error(result.error || '推演药路失败'); + } + + if (!result.data.valid) { + clearFormulaAnalysis(); + setFormulaAnalysisError(result.data.staticBlockingReason || '当前炉材未通过炉前验材。'); + return; + } + + setFormulaAnalysis(result.data); + setIsFormulaAnalysisModalOpen(true); + setAnalysisExpiresAfterMs(result.data.expiresInSeconds * 1000); + analyzedFormulaSelectionKeyRef.current = currentFormulaSelectionKey; + setAnalysisCooldownRemaining(result.data.cooldownRemainingSeconds); + pushToast({ + message: + result.data.fitBand === 'aligned' + ? '药路已明,可依方成丹。' + : result.data.fitBand === 'degraded' + ? '这炉尚可循方,但药力会有折损。' + : '这炉偏离丹方较远,仍可强行收丹。', + tone: + result.data.fitBand === 'poor' + ? 'warning' + : result.data.fitBand === 'degraded' + ? 'default' + : 'success', + }); + } catch (error) { + clearFormulaAnalysis({ keepError: true }); + setFormulaAnalysisError( + error instanceof Error ? error.message : '推演药路失败,请稍后再试。', + ); + pushToast({ + message: + error instanceof Error ? error.message : '推演药路失败,请稍后再试。', + tone: 'danger', + }); + } finally { + setIsAnalyzingFormula(false); + } + }; + + const handleSubmit = async () => { + if (!cultivator) { + pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); + return; + } + if (activeMode === 'formula' && !selectedFormulaId) { + pushToast({ message: '请先选定丹方。', tone: 'warning' }); + return; + } + if (activeMode === 'formula' && !formulaAnalysis?.analysisId) { + pushToast({ message: '请先推演药路。', tone: 'warning' }); + return; + } + if (selectedMaterialIds.length === 0) { + pushToast({ message: '丹炉已备,只欠灵材。', tone: 'warning' }); + return; + } + if (activeMode === 'improvised' && !userPrompt.trim()) { + pushToast({ message: '请先注入丹意。', tone: 'warning' }); + return; + } + if (previewError || validation?.valid === false || !displayCanAfford) { + pushToast({ message: '当前炉况未稳,暂不可开炉。', tone: 'warning' }); + return; + } + openQiActionConfirm({ + actionName: activeMode === 'formula' ? '丹方炼丹' : '开炉炼丹', + qiCost, + confirmLabel: activeMode === 'formula' ? '依方成丹' : '开炉炼丹', + onConfirm: async () => { + setSubmitting(true); + setStatus( + activeMode === 'formula' + ? '丹方引火,炉势循脉而行……' + : '地火回环,药性相搏……', + ); + setCreatedConsumable(null); + setFormulaDiscovery(null); + setFormulaProgress(null); + setIsResultModalOpen(false); + setIsDiscoveryModalOpen(false); + + try { + const result = await mutate>( + fetch('/api/craft', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(submitPayload), + }), + ); + + if (!result.consumable) { + throw new Error('炼丹失败'); + } + + const nextConsumable = result.consumable; + const discoveredFormula = result.formulaDiscovery ?? null; + const successMessage = `【${nextConsumable.name}】丹成 ${nextConsumable.quantity} 枚!`; + setCreatedConsumable(nextConsumable); + setFormulaDiscovery(discoveredFormula); + setFormulaProgress(result.formulaProgress ?? null); + setIsResultModalOpen(true); + setIsDiscoveryModalOpen(false); + setCelebrationTick((prev) => prev + 1); + setStatus(successMessage); + pushToast({ message: successMessage, tone: 'success' }); + setSelectedMaterialIds([]); + setSelectedMaterialMap({}); + setDoseMap({}); + setIsMaterialModalOpen(false); + if (activeMode === 'improvised') { + setUserPrompt(''); + } + setPreviewState(DEFAULT_PREVIEW_STATE); + clearFormulaAnalysis(); + setMaterialsRefreshKey((prev) => prev + 1); + } catch (error) { + if (error instanceof Error && error.message.includes('请先推演药路')) { + clearFormulaAnalysis({ keepError: true }); + setFormulaAnalysisError(error.message); + } + const failMessage = + error instanceof Error + ? `炸炉了:${error.message}` + : '炼丹失败,请稍后再试。'; + setStatus(failMessage); + pushToast({ message: failMessage, tone: 'danger' }); + } finally { + setSubmitting(false); + } + }, + }); + }; + + const handleDiscoveryDecision = async (accept: boolean) => { + if (!formulaDiscovery) { + return; + } + + setIsHandlingDiscovery(true); + try { + const response = await fetch('/api/alchemy/formulas/discovery/confirm', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + token: formulaDiscovery.token, + accept, + }), + }); + const result: DiscoveryConfirmResponse = await response.json(); + + if (!response.ok || !result.success || !result.data) { + throw new Error(result.error || '丹方确认失败'); + } + + if (accept && result.data.saved && result.data.formula) { + setSelectedFormulaId(result.data.formula.id); + setSelectedFormulaSnapshot(result.data.formula); + setFormulaPage(1); + await loadFormulas({ page: 1 }); + pushToast({ + message: `已悟得【${result.data.formula.name}】`, + tone: 'success', + }); + } else if (!accept) { + pushToast({ + message: '丹意散去,未留成方。', + tone: 'default', + }); + } + + setFormulaDiscovery(null); + setIsDiscoveryModalOpen(false); + } catch (error) { + pushToast({ + message: + error instanceof Error ? error.message : '丹方确认失败,请稍后再试。', + tone: 'danger', + }); + } finally { + setIsHandlingDiscovery(false); + } + }; + + const openDeleteFormulaConfirm = (formula: AlchemyFormula) => { + setDialog({ + id: `delete-formula-${formula.id}`, + title: '删除丹方', + content: ( +
+

+ 确定要删去{' '} + {formula.name}{' '} + 吗? +

+

+ 删除后将无法恢复,但已炼成丹药的来源记述不会受影响。 +

+
+ ), + confirmLabel: '删除丹方', + cancelLabel: '作罢', + loading: isDeletingFormula, + loadingLabel: '删除中……', + onConfirm: async () => { + if (isDeletingFormula) { + return; + } + + try { + setIsDeletingFormula(true); + setDialog((currentDialog) => + currentDialog + ? { + ...currentDialog, + loading: true, + } + : currentDialog, + ); + + const response = await fetch(`/api/alchemy/formulas/${formula.id}`, { + method: 'DELETE', + }); + const result: FormulaDeleteResponse = await response.json(); + + if (!response.ok || !result.success) { + throw new Error(result.error || '丹方删除失败'); + } + + setPreviewState(DEFAULT_PREVIEW_STATE); + clearFormulaAnalysis(); + if (selectedFormulaId === formula.id) { + setSelectedFormulaId(null); + setSelectedFormulaSnapshot(null); + } + await loadFormulas({ + page: + formulas.length === 1 && formulaPagination.hasPreviousPage + ? formulaPagination.page - 1 + : formulaPagination.page, + }); + pushToast({ + message: result.message || `已删除【${formula.name}】`, + tone: 'success', + }); + } catch (error) { + pushToast({ + message: + error instanceof Error + ? error.message + : '丹方删除失败,请稍后再试。', + tone: 'danger', + }); + } finally { + setIsDeletingFormula(false); + setDialog((currentDialog) => + currentDialog + ? { + ...currentDialog, + loading: false, + } + : currentDialog, + ); + } + }, + }); + }; + + if (isLoading && !cultivator) { + return ( +
+

丹火温养中……

+
+ ); + } + + const headerStatus = + activeMode === 'formula' && selectedFormula + ? `已选丹方:${selectedFormula.name}` + : selectedMaterialIds.length > 0 + ? `已投入 ${selectedMaterialIds.length} 种灵材` + : activeMode === 'formula' + ? '请先选定丹方,再投入灵材。' + : '请投入灵材并注入丹意。'; + const handlePrimaryAction = () => { + if (isFormulaMode) { + if (hasCraftableFormulaAnalysis) { + void handleSubmit(); + return; + } + void handleAnalyzeFormula(); + return; + } + + void handleSubmit(); + }; + + return ( + + {sectContext ? navigate('/game/sect')} variant="secondary">返回宗门总视图 : null} + {note ? ( + +

{note}

+
+ ) : null} +
+

+ {headerStatus} +

+ setIsGuideModalOpen(true)} + className="shrink-0" + > + 炉理指引 + +
+ + } + aside={ + <> + {sectContext ? ( + +
+

丹器坊等级:{sectContext.facilityLevel}级

+

当前灵石减免:{sectContext.discountPercent}%

+
+
+ ) : null} + +
+

先选青露草、凝水花一类凡品灵草,投入 1 份即可。

+

丹意可写:疗伤回元,药性温和。

+

第一炉只求看懂成丹结果,不必追求高品阶。

+ {isStarterAlchemyTask ? ( + { + setActiveMode('improvised'); + setUserPrompt(STARTER_ALCHEMY_PROMPT); + setPreviewState(DEFAULT_PREVIEW_STATE); + clearFormulaAnalysis(); + setIsMaterialModalOpen(true); + }} + disabled={isSubmitting} + > + 选择首炉灵材 + + ) : null} +
+
+ + +
+

+ 当前炉法:{activeMode === 'formula' ? '丹方炼制' : '即兴炼丹'} +

+

+ 已投入灵材:{selectedMaterialIds.length}/{MAX_MATERIALS} +

+

灵石余额:{cultivator?.spirit_stones ?? 0}

+ {estimatedSpiritStones !== null ? ( +

预计耗费:{estimatedSpiritStones} 灵石

+ ) : null} + {displayBatchPreview ? ( +

+ 预计出丹:{displayBatchPreview.minYield} + {displayBatchPreview.maxYield !== displayBatchPreview.minYield + ? `-${displayBatchPreview.maxYield}` + : ''}{' '} + 枚 +

+ ) : null} +
+
+ + + {activeMode === 'formula' && selectedFormula ? ( +
+

已选丹方:{selectedFormula.name}

+

丹方族类:{getPillFamilyLabel(selectedFormula.family)}

+

熟练等级:Lv.{selectedFormula.mastery.level}

+

+ {formulaAnalysis + ? getFormulaAnalysisNarrative(formulaAnalysis.fitBand) + : '丹方已定,只待这一炉药性显出真章。'} +

+
+ ) : activeMode === 'formula' ? ( +

请先选定丹方,再投入灵材。

+ ) : ( +

请投入灵材并注入丹意,炉火会顺着材料药性与这缕心意凝成丹形。

+ )} + {previewError ? ( +

{previewError}

+ ) : null} + {displayValidation?.blockingReason ? ( +

+ {displayValidation.blockingReason} +

+ ) : null} +
+ + } + contentClassName={sectContext ? 'bg-[radial-gradient(circle_at_50%_100%,rgba(190,65,18,0.16),transparent_32%),linear-gradient(145deg,rgba(184,124,54,0.08),transparent_46%)] px-3 py-4 sm:px-5' : undefined} + > + + + {activeMode === 'formula' ? ( + + {selectedFormula ? ( +
+ + setIsFormulaSelectionModalOpen(true)} + disabled={isSubmitting} + > + 更换丹方 + + {formulaAnalysis ? ( + +
+ + 药路已推演:{getFormulaFitBandLabel(formulaAnalysis.fitBand)} + ,合方程度 {Math.round(formulaAnalysis.fitScore * 100)}%。 + + {formulaAnalysis.batchProfile ? ( + + 本炉预计 {formulaAnalysis.batchProfile.yieldQuantity}{' '} + 枚,配伍 + {getBatchTierLabel( + formulaAnalysis.batchProfile.compoundTier, + )} + 。 + + ) : null} + setIsFormulaAnalysisModalOpen(true)} + disabled={isSubmitting} + > + 查看点评 + +
+
+ ) : formulaAnalysisError ? ( + {formulaAnalysisError} + ) : selectedMaterialIds.length > 0 ? ( + + 灵材既入炉中,还需先推演药路,再可依方收丹。 + + ) : null} +
+ ) : ( +
+ + 先选定一份丹方,再安排炉材。 + + setIsFormulaSelectionModalOpen(true)} + disabled={isSubmitting} + > + 选择丹方 + +
+ )} +
+ ) : null} + + +
+

+ 已投入 {selectedMaterialIds.length} / {MAX_MATERIALS} 种灵材 +

+ setIsMaterialModalOpen(true)} + disabled={!canChooseMaterials} + > + 打开材料 + +
+ {isFormulaMode && !selectedFormulaId ? ( + + 先选丹方,再甄选这一炉要投入的灵材。 + + ) : null} + toggleMaterial(id)} + onDoseChange={handleDoseChange} + /> +
+ + {activeMode === 'improvised' ? ( + + + + ) : null} + + + {estimatedSpiritStones !== null ? ( +
+ + {SPIRIT_STONES_LABEL}: + + {estimatedSpiritStones} + {' '} + 枚 + + + {displayCanAfford ? '✓ 资源充足' : '✗ 灵石不足'} + +
+ ) : ( + + {activeMode === 'formula' + ? '请先选定丹方并投入材料,过一遍炉前验材。' + : '请先选择材料以查看本炉消耗。'} + + )} + + {displayBatchPreview ? ( + + {displayBatchPreview.summary} 预计出丹{' '} + {displayBatchPreview.minYield} + {displayBatchPreview.maxYield !== displayBatchPreview.minYield + ? `-${displayBatchPreview.maxYield}` + : ''}{' '} + 枚;共 {displayBatchPreview.materialKindCount} 种灵材,合计{' '} + {displayBatchPreview.totalDose} 份。 + + ) : null} + + {previewError && {previewError}} + {displayValidation?.blockingReason && ( + + {displayValidation.blockingReason} + + )} + {displayPreviewWarnings.map((warning) => ( + + {warning} + + ))} +
+ + + + 重置 + + + {isFormulaMode + ? formulaPrimaryButtonLabel + : isSubmitting + ? '丹火炼中……' + : '开炉炼丹'} + + + + {status && !isResultModalOpen && ( +
+ {status} +
+ )} + + setIsMaterialModalOpen(false)} + title="甄选炼丹灵材" + maxMaterials={MAX_MATERIALS} + cultivatorId={cultivator?.id} + selectedMaterialIds={selectedMaterialIds} + onToggleMaterial={toggleMaterial} + selectedMaterialMap={selectedMaterialMap} + isSubmitting={isSubmitting} + pageSize={20} + includeMaterialTypes={[...ALLOWED_MATERIAL_TYPES] as MaterialType[]} + refreshKey={materialsRefreshKey} + loadingText="正在检索储物袋中的灵材,请稍候……" + emptyNoticeText="暂无可用于炼丹的材料。" + totalText={(total) => `共 ${total} 份可用于炼丹的材料`} + /> + + setIsFormulaSelectionModalOpen(false)} + formulas={formulas} + selectedFormulaId={selectedFormulaId} + isLoading={isLoadingFormulas} + error={formulasError} + search={formulaSearch} + familyFilter={formulaFamilyFilter} + pagination={formulaPagination} + isDeleting={isDeletingFormula} + isSubmitting={isSubmitting} + onSearchChange={(value) => { + setFormulaSearch(value); + setFormulaPage(1); + }} + onFamilyFilterChange={(value) => { + setFormulaFamilyFilter(value); + setFormulaPage(1); + }} + onPageChange={setFormulaPage} + onSelectFormula={(formula) => { + clearFormulaAnalysis(); + setSelectedFormulaId(formula.id); + setSelectedFormulaSnapshot(formula); + setPreviewState(DEFAULT_PREVIEW_STATE); + setIsFormulaSelectionModalOpen(false); + }} + onDeleteFormula={openDeleteFormulaConfirm} + /> + + { + setIsResultModalOpen(false); + if (formulaDiscovery) { + setIsDiscoveryModalOpen(true); + } + }} + viewerRealm={cultivator?.realm} + /> + + void handleDiscoveryDecision(true)} + onRejectDiscovery={() => void handleDiscoveryDecision(false)} + /> + + setIsFormulaAnalysisModalOpen(false)} + onCraft={() => { + setIsFormulaAnalysisModalOpen(false); + void handleSubmit(); + }} + /> + + setIsGuideModalOpen(false)} + /> + + { + if (!isDeletingFormula) { + setDialog(null); + } + }} + /> + + {celebrationTick > 0 && ( + + )} +
+ ); +} + +export default AlchemyScene; diff --git a/src/react-app/components/feature/craft/RefineScene.tsx b/src/react-app/components/feature/craft/RefineScene.tsx new file mode 100644 index 00000000..1882e10f --- /dev/null +++ b/src/react-app/components/feature/craft/RefineScene.tsx @@ -0,0 +1,434 @@ +import { + CreationIntentPanel, + CreationProductResultModal, + MaterialSelectionModal, + SelectedMaterialsWithDose, + type CreationProductResultRecord, +} from '@app/components/feature/creation'; +import { + GameSceneAsideSection, + GameSceneFrame, + GameSceneNote, + GameSceneSection, +} from '@app/components/game-shell'; +import { useInkUI } from '@app/components/providers/InkUIProvider'; +import { + InkActionGroup, + InkButton, + InkIdentifyCelebration, + InkNotice, +} from '@app/components/ui'; +import { + useQiActionConfirm, +} from '@app/components/feature/cultivator/useQiActionConfirm'; +import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; +import { CREATION_INPUT_CONSTRAINTS } from '@shared/engine/creation-v2/config/CreationBalance'; +import { getAllowedMaterialTypesForCraftType } from '@shared/engine/creation-v2/config/CreationCraftPolicy'; +import { usePlayerStateView } from '@app/lib/player-state/selectors'; +import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; +import { usePlayerStateActions } from '@app/lib/player-state/store'; +import type { EquipmentSlot } from '@shared/types/constants'; +import type { Material } from '@shared/types/cultivator'; +import { useEffect, useMemo, useState } from 'react'; +import { useNavigate } from 'react-router'; + +const CRAFT_TYPE = 'refine' as const; +const ALLOWED_MATERIAL_TYPES = [...getAllowedMaterialTypesForCraftType(CRAFT_TYPE)]; +const MAX_MATERIALS = CREATION_INPUT_CONSTRAINTS.maxMaterialKinds; +const MIN_DOSE = CREATION_INPUT_CONSTRAINTS.minQuantityPerMaterial; +const MAX_DOSE = CREATION_INPUT_CONSTRAINTS.maxQuantityPerMaterial; +const SPIRIT_STONES_LABEL = getGameConceptLabel('spirit_stones'); + +type CostEstimate = { + spiritStones?: number; + comprehension?: number; +}; + +type CostResponse = { + success: boolean; + data?: { + cost: CostEstimate; + canAfford: boolean; + validation: PreviewValidation | null; + }; +}; + +type PreviewValidation = { + valid: boolean; + blockingReason?: string; + warnings: string[]; + missingMatchingManual: boolean; +}; + +export type RefineSceneProps = { + sectContext?: { + facilityLevel: number; + discountPercent: number; + }; +}; + +export function RefineScene({ sectContext }: RefineSceneProps) { + const { cultivator, note, isLoading } = usePlayerStateView(); + const { mutate } = usePlayerStateActions(); + const [selectedMaterialIds, setSelectedMaterialIds] = useState([]); + const [selectedMaterialMap, setSelectedMaterialMap] = useState< + Record + >({}); + const [doseMap, setDoseMap] = useState>({}); + const [userPrompt, setUserPrompt] = useState(''); + const [requestedSlot, setRequestedSlot] = useState(''); + const [status, setStatus] = useState(''); + const [isSubmitting, setSubmitting] = useState(false); + const [createdResult, setCreatedResult] = + useState(null); + const [isResultModalOpen, setIsResultModalOpen] = useState(false); + const [isMaterialModalOpen, setIsMaterialModalOpen] = useState(false); + const [celebrationTick, setCelebrationTick] = useState(0); + const [materialsRefreshKey, setMaterialsRefreshKey] = useState(0); + const [estimatedCost, setEstimatedCost] = useState(null); + const [validation, setValidation] = useState(null); + const [canAfford, setCanAfford] = useState(true); + const { pushToast } = useInkUI(); + const { openQiActionConfirm } = useQiActionConfirm(); + const navigate = useNavigate(); + + useEffect(() => { + if (selectedMaterialIds.length === 0) { + return; + } + + let cancelled = false; + + const loadCostEstimate = async () => { + try { + const response = await fetch( + `/api/craft?craftType=${CRAFT_TYPE}&materialIds=${selectedMaterialIds.join(',')}`, + ); + const result: CostResponse = await response.json(); + if (!cancelled && result.success && result.data) { + setEstimatedCost(result.data.cost); + setCanAfford(result.data.canAfford); + setValidation(result.data.validation); + } + } catch (error) { + if (!cancelled) { + setValidation(null); + console.error('Failed to fetch cost estimate:', error); + } + } + }; + + void loadCostEstimate(); + + return () => { + cancelled = true; + }; + }, [selectedMaterialIds]); + + const toggleMaterial = (id: string, material?: Material) => { + setSelectedMaterialIds((prev) => { + if (prev.includes(id)) { + setSelectedMaterialMap((map) => { + const next = { ...map }; + delete next[id]; + return next; + }); + setDoseMap((map) => { + const next = { ...map }; + delete next[id]; + return next; + }); + return prev.filter((mid) => mid !== id); + } + if (prev.length >= MAX_MATERIALS) { + pushToast({ + message: `炼器炉量力有限,最多投入 ${MAX_MATERIALS} 种灵材`, + tone: 'warning', + }); + return prev; + } + if (material) { + setSelectedMaterialMap((map) => ({ ...map, [id]: material })); + setDoseMap((map) => ({ ...map, [id]: MIN_DOSE })); + } + return [...prev, id]; + }); + }; + + const handleDoseChange = (id: string, dose: number) => { + const material = selectedMaterialMap[id]; + if (!material) return; + const stock = material.quantity ?? 0; + const clamped = Math.min( + Math.min(MAX_DOSE, Math.max(stock, MIN_DOSE)), + Math.max(MIN_DOSE, Math.floor(dose)), + ); + setDoseMap((prev) => ({ ...prev, [id]: clamped })); + }; + + const resetAll = () => { + setStatus(''); + setCreatedResult(null); + setIsResultModalOpen(false); + setSelectedMaterialIds([]); + setSelectedMaterialMap({}); + setDoseMap({}); + setUserPrompt(''); + setRequestedSlot(''); + setValidation(null); + setIsMaterialModalOpen(false); + }; + + const submitPayload = useMemo( + () => ({ + materialIds: selectedMaterialIds, + craftType: CRAFT_TYPE, + materialQuantities: Object.fromEntries( + selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), + ), + userPrompt: userPrompt.trim() || undefined, + requestedSlot: requestedSlot || undefined, + }), + [selectedMaterialIds, doseMap, userPrompt, requestedSlot], + ); + + const displayEstimatedCost = + selectedMaterialIds.length > 0 ? estimatedCost : null; + const displayValidation = selectedMaterialIds.length > 0 ? validation : null; + const displayCanAfford = selectedMaterialIds.length > 0 ? canAfford : true; + const qiCost = QI_ACTION_COSTS.creation_artifact; + + const handleSubmit = async () => { + if (!cultivator) { + pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); + return; + } + + if (selectedMaterialIds.length === 0) { + pushToast({ message: '巧妇难为无米之炊,请投入灵材。', tone: 'warning' }); + return; + } + + if (!requestedSlot) { + pushToast({ message: '请选择目标槽位以确定法宝类型。', tone: 'warning' }); + return; + } + + openQiActionConfirm({ + actionName: '开炉炼器', + qiCost, + confirmLabel: '开炉炼器', + onConfirm: async () => { + setSubmitting(true); + setStatus('炉火纯青,真火锤锻……'); + setCreatedResult(null); + setIsResultModalOpen(false); + + try { + const artifact = await mutate( + fetch('/api/craft', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(submitPayload), + }), + ); + const successMessage = `【${artifact.name}】出世!`; + setCreatedResult(artifact); + setIsResultModalOpen(true); + setCelebrationTick((prev) => prev + 1); + setStatus(successMessage); + pushToast({ message: successMessage, tone: 'success' }); + setSelectedMaterialIds([]); + setSelectedMaterialMap({}); + setDoseMap({}); + setIsMaterialModalOpen(false); + setMaterialsRefreshKey((prev) => prev + 1); + } catch (error) { + const failMessage = + error instanceof Error + ? `炸炉了:${error.message}` + : '炼制失败,请稍后再试。'; + setStatus(failMessage); + pushToast({ message: failMessage, tone: 'danger' }); + } finally { + setSubmitting(false); + } + }, + }); + }; + + if (isLoading && !cultivator) { + return ( +
+

地火引动中……

+
+ ); + } + + return ( + + {sectContext ? navigate('/game/sect')} variant="secondary">返回宗门总视图 : null} + {note ?

{note}

: null} + + ) : undefined + } + aside={ + <> + {sectContext ? ( + +
+

丹器坊等级:{sectContext.facilityLevel}级

+

当前灵石减免:{sectContext.discountPercent}%

+
+
+ ) : null} + +
+

已投入灵材:{selectedMaterialIds.length} / {MAX_MATERIALS}

+

目标槽位:{requestedSlot ? '已选定' : '尚未指定'}

+

预计灵石:{displayEstimatedCost?.spiritStones ?? 0}

+
+
+ +

槽位决定器型,灵材决定骨相,意念决定成品气质。

+

若资源不足或规则冲突,先在主区修正后再起炉。

+ + ), + }} + /> + + } + contentClassName={sectContext ? 'bg-[radial-gradient(circle_at_82%_8%,rgba(130,54,27,0.14),transparent_26%),linear-gradient(135deg,rgba(52,59,60,0.08),transparent_44%)] px-3 py-4 sm:px-5' : undefined} + > + + + + + +
+

+ 已投入 {selectedMaterialIds.length} / {MAX_MATERIALS} 种灵材 +

+ setIsMaterialModalOpen(true)} + disabled={isSubmitting} + > + 打开材料 + +
+ toggleMaterial(id)} + onDoseChange={handleDoseChange} + /> +
+ + + {displayEstimatedCost ? ( +
+ + {SPIRIT_STONES_LABEL}: + + {displayEstimatedCost.spiritStones} + {' '} + 枚 + + + {displayCanAfford ? '✓ 资源充足' : '✗ 灵石不足'} + +
+ ) : ( + 请先选择材料以查看消耗 + )} + + {displayValidation?.blockingReason && ( + {displayValidation.blockingReason} + )} +
+ + + + + 重置 + + + {isSubmitting ? '真火炼中……' : '开炉炼器'} + + + + + {status && !createdResult && ( +
+ {status} +
+ )} + + setIsMaterialModalOpen(false)} + title="甄选炼器灵材" + maxMaterials={MAX_MATERIALS} + cultivatorId={cultivator?.id} + selectedMaterialIds={selectedMaterialIds} + onToggleMaterial={toggleMaterial} + selectedMaterialMap={selectedMaterialMap} + isSubmitting={isSubmitting} + pageSize={20} + includeMaterialTypes={ALLOWED_MATERIAL_TYPES} + refreshKey={materialsRefreshKey} + loadingText="正在检索储物袋中的灵材,请稍候……" + emptyNoticeText="暂无可用于炼器的材料。" + totalText={(total) => `共 ${total} 份可用于炼器的材料`} + /> + + setIsResultModalOpen(false)} + product={createdResult} + /> + + {celebrationTick > 0 && ( + + )} +
+ ); +} + +export default RefineScene; diff --git a/src/react-app/routes/game/retreat/components/BreakthroughConfirmModal.tsx b/src/react-app/components/feature/retreat/BreakthroughConfirmModal.tsx similarity index 97% rename from src/react-app/routes/game/retreat/components/BreakthroughConfirmModal.tsx rename to src/react-app/components/feature/retreat/BreakthroughConfirmModal.tsx index e46fb0ea..cfdad49e 100644 --- a/src/react-app/routes/game/retreat/components/BreakthroughConfirmModal.tsx +++ b/src/react-app/components/feature/retreat/BreakthroughConfirmModal.tsx @@ -1,7 +1,7 @@ import { InkModal } from '@app/components/layout'; import { InkButton } from '@app/components/ui/InkButton'; import { format } from 'd3-format'; -import type { BreakthroughChancePreviewData } from '../hooks/useRetreatViewModel'; +import type { BreakthroughChancePreviewData } from './useRetreatViewModel'; interface BreakthroughConfirmModalProps { isOpen: boolean; diff --git a/src/react-app/routes/game/retreat/components/RetreatResultModal.tsx b/src/react-app/components/feature/retreat/RetreatResultModal.tsx similarity index 96% rename from src/react-app/routes/game/retreat/components/RetreatResultModal.tsx rename to src/react-app/components/feature/retreat/RetreatResultModal.tsx index 9f537227..6ed56eb0 100644 --- a/src/react-app/routes/game/retreat/components/RetreatResultModal.tsx +++ b/src/react-app/components/feature/retreat/RetreatResultModal.tsx @@ -10,7 +10,7 @@ import type { Attributes } from '@shared/types/cultivator'; import { getAttributeInfo } from '@shared/lib/gameConceptDisplay'; import { format } from 'd3-format'; import { useMemo } from 'react'; -import { isSuccessfulBreakthrough } from '../lib/retreatStream'; +import { isSuccessfulBreakthrough } from './retreatStream'; const COMPREHENSION_LABEL = getGameConceptLabel('comprehension_insight'); @@ -21,6 +21,7 @@ interface RetreatResultModalProps { celebrationTick: number; onClose: () => void; onGoReincarnate: () => void; + allowAttributeNavigation?: boolean; } export function RetreatResultModal({ @@ -30,6 +31,7 @@ export function RetreatResultModal({ celebrationTick, onClose, onGoReincarnate, + allowAttributeNavigation = true, }: RetreatResultModalProps) { if (!retreatResult) { return null; @@ -60,7 +62,7 @@ export function RetreatResultModal({ className="max-w-2xl" footer={
- {canGoAllocateAttributes ? ( + {canGoAllocateAttributes && allowAttributeNavigation ? ( {primaryLabel} diff --git a/src/react-app/routes/game/retreat/components/RetreatView.tsx b/src/react-app/components/feature/retreat/RetreatView.tsx similarity index 90% rename from src/react-app/routes/game/retreat/components/RetreatView.tsx rename to src/react-app/components/feature/retreat/RetreatView.tsx index 2b598108..1e65e6d2 100644 --- a/src/react-app/routes/game/retreat/components/RetreatView.tsx +++ b/src/react-app/components/feature/retreat/RetreatView.tsx @@ -12,15 +12,16 @@ import type { TaskInstance } from '@shared/types/task'; import { useRetreatViewModel, type CultivationProgressData, -} from '../hooks/useRetreatViewModel'; +} from './useRetreatViewModel'; import { BreakthroughConfirmModal } from './BreakthroughConfirmModal'; import { RetreatResultModal } from './RetreatResultModal'; import { cn } from '@shared/lib/utils'; import { useState } from 'react'; +import { useNavigate } from 'react-router'; import type { RetreatBuffTag, RetreatEfficiencyModel, -} from '../lib/retreatEfficiency'; +} from './retreatEfficiency'; const COMPREHENSION_LABEL = getGameConceptLabel('comprehension_insight'); @@ -128,9 +129,11 @@ function RetreatSummaryEntry({ function RetreatBuffTags({ tags, emptyHint, + showShortcuts = true, }: { tags: RetreatBuffTag[]; emptyHint?: string | null; + showShortcuts?: boolean; }) { if (tags.length === 0 && !emptyHint) return null; @@ -154,12 +157,12 @@ function RetreatBuffTags({ {emptyHint} - - 背包 - - - 炼丹 - + {showShortcuts ? ( + <> + 背包 + 炼丹 + + ) : null} ) : null}
@@ -247,6 +250,7 @@ function RetreatCultivationPanel({ retreatLoading, onRetreatYearsChange, onRetreat, + showShortcuts, }: { isMajorBreakthrough: boolean; majorBreakthroughBlocked: boolean; @@ -256,6 +260,7 @@ function RetreatCultivationPanel({ retreatLoading: boolean; onRetreatYearsChange: (value: string) => void; onRetreat: () => Promise; + showShortcuts: boolean; }) { return (
@@ -281,6 +286,7 @@ function RetreatCultivationPanel({
@@ -397,8 +403,16 @@ function BreakthroughPanel({ ); } -export function RetreatView() { +export type RetreatViewProps = { + sectContext?: { + facilityLevel: number; + experienceBonusPercent: number; + }; +}; + +export function RetreatView({ sectContext }: RetreatViewProps) { const [activeTab, setActiveTab] = useState('retreat'); + const navigate = useNavigate(); const { cultivator, isLoading, @@ -429,6 +443,12 @@ export function RetreatView() { } = useRetreatViewModel(); const shouldHoldResultShell = !cultivator && retreatResultOpen && Boolean(retreatResult?.depleted); + const headerMeta = sectContext || note ? ( +
+ {sectContext ? navigate('/game/sect')} variant="secondary">返回宗门总视图 : null} + {note ?

{note}

: null} +
+ ) : undefined; if (isLoading && !cultivator && !shouldHoldResultShell) { return ( @@ -442,14 +462,8 @@ export function RetreatView() { if (shouldHoldResultShell) { return ( -

{note}

- - ) : undefined - } + title={sectContext ? '宗门修炼室' : '静室修行'} + headerMeta={headerMeta} >
@@ -464,6 +478,7 @@ export function RetreatView() { celebrationTick={celebrationTick} onClose={closeRetreatResult} onGoReincarnate={handleGoReincarnate} + allowAttributeNavigation={!sectContext} /> ); @@ -499,14 +514,11 @@ export function RetreatView() { return ( -

{note}

- - ) : undefined - } + title={sectContext ? '宗门修炼室' : '静室修行'} + description={sectContext ? '聚灵阵纹绕蒲团缓缓流转,静香已燃;定下闭关年数,宗门灵气会在结算时自然汇入。' : undefined} + headerMeta={headerMeta} + aside={sectContext ?

修炼室等级:{sectContext.facilityLevel}级

闭关修为加成:+{sectContext.experienceBonusPercent}%

: undefined} + contentClassName={sectContext ? 'bg-[radial-gradient(circle_at_50%_18%,rgba(64,148,135,0.16)_0_10%,transparent_11%_25%,rgba(64,148,135,0.08)_26%_27%,transparent_28%),linear-gradient(180deg,rgba(205,228,215,0.22),transparent_55%)] px-3 py-4 sm:px-5' : undefined} > ) : (
); diff --git a/src/react-app/routes/game/retreat/lib/retreatEfficiency.test.ts b/src/react-app/components/feature/retreat/retreatEfficiency.test.ts similarity index 100% rename from src/react-app/routes/game/retreat/lib/retreatEfficiency.test.ts rename to src/react-app/components/feature/retreat/retreatEfficiency.test.ts diff --git a/src/react-app/routes/game/retreat/lib/retreatEfficiency.ts b/src/react-app/components/feature/retreat/retreatEfficiency.ts similarity index 100% rename from src/react-app/routes/game/retreat/lib/retreatEfficiency.ts rename to src/react-app/components/feature/retreat/retreatEfficiency.ts diff --git a/src/react-app/routes/game/retreat/lib/retreatStream.test.ts b/src/react-app/components/feature/retreat/retreatStream.test.ts similarity index 100% rename from src/react-app/routes/game/retreat/lib/retreatStream.test.ts rename to src/react-app/components/feature/retreat/retreatStream.test.ts diff --git a/src/react-app/routes/game/retreat/lib/retreatStream.ts b/src/react-app/components/feature/retreat/retreatStream.ts similarity index 100% rename from src/react-app/routes/game/retreat/lib/retreatStream.ts rename to src/react-app/components/feature/retreat/retreatStream.ts diff --git a/src/react-app/routes/game/retreat/hooks/useRetreatViewModel.tsx b/src/react-app/components/feature/retreat/useRetreatViewModel.tsx similarity index 99% rename from src/react-app/routes/game/retreat/hooks/useRetreatViewModel.tsx rename to src/react-app/components/feature/retreat/useRetreatViewModel.tsx index f484213d..bbdd60ca 100644 --- a/src/react-app/routes/game/retreat/hooks/useRetreatViewModel.tsx +++ b/src/react-app/components/feature/retreat/useRetreatViewModel.tsx @@ -23,11 +23,11 @@ import { type ReincarnateContextData, consumeRetreatStream, isSuccessfulBreakthrough, -} from '../lib/retreatStream'; +} from './retreatStream'; import { buildRetreatEfficiencyModel, type RetreatEfficiencyModel, -} from '../lib/retreatEfficiency'; +} from './retreatEfficiency'; export interface CultivationProgressData { cultivation_exp: number; diff --git a/src/react-app/components/game-shell/gameNavigation.test.ts b/src/react-app/components/game-shell/gameNavigation.test.ts index d86d1546..95f22fec 100644 --- a/src/react-app/components/game-shell/gameNavigation.test.ts +++ b/src/react-app/components/game-shell/gameNavigation.test.ts @@ -75,7 +75,7 @@ describe('game navigation dock groups', () => { ); expect(getGameSceneMeta('sect-abilities')).toMatchObject({ id: 'sect-abilities', - label: '宗门神通', + label: '演武台', group: 'cultivation', }); expect(expandedIds).toContain('sect-abilities'); diff --git a/src/react-app/components/game-shell/gameNavigation.ts b/src/react-app/components/game-shell/gameNavigation.ts index ffcdb0cd..0b9dfafb 100644 --- a/src/react-app/components/game-shell/gameNavigation.ts +++ b/src/react-app/components/game-shell/gameNavigation.ts @@ -92,8 +92,8 @@ export const gameDockGroups: GameNavGroup[] = [ }, { id: 'sect-abilities', - sceneLabel: '宗门神通', - href: '/game/sect/abilities', + sceneLabel: '演武台', + href: '/game/sect/arena', expandedDockLabel: '🗡️ 宗门神通', }, { @@ -102,6 +102,19 @@ export const gameDockGroups: GameNavGroup[] = [ href: '/game/sect', expandedDockLabel: '⛰️ 宗门', }, + { id: 'sect-hall', sceneLabel: '宗门大殿' }, + { id: 'sect-affairs', sceneLabel: '执事堂' }, + { id: 'sect-archive', sceneLabel: '藏经阁' }, + { id: 'sect-enlightenment-cliff', sceneLabel: '悟道崖' }, + { id: 'sect-treasury', sceneLabel: '宗门宝库' }, + { id: 'sect-industries', sceneLabel: '百业院' }, + { id: 'sect-cultivation-room', sceneLabel: '宗门修炼室' }, + { id: 'sect-alchemy', sceneLabel: '宗门丹房' }, + { id: 'sect-refinery', sceneLabel: '宗门器坊' }, + { id: 'sect-spirit-vein', sceneLabel: '灵脉矿场' }, + { id: 'sect-herb-garden', sceneLabel: '宗门药田' }, + { id: 'sect-cave', sceneLabel: '宗门洞府' }, + { id: 'sect-gate', sceneLabel: '宗门山门' }, { id: 'training-room', sceneLabel: '练功房', diff --git a/src/react-app/lib/router/gameShellRegistry.test.ts b/src/react-app/lib/router/gameShellRegistry.test.ts index 713ccf8f..72a5bbdd 100644 --- a/src/react-app/lib/router/gameShellRegistry.test.ts +++ b/src/react-app/lib/router/gameShellRegistry.test.ts @@ -30,6 +30,11 @@ describe('game shell registry', () => { expect(resolveGameShellKind('/game')).toBe('viewport'); expect(resolveGameShellKind('/game/inventory')).toBe('viewport'); expect(resolveGameShellKind('/game/sect/abilities')).toBe('viewport'); + expect(resolveGameShellKind('/game/sect/hall')).toBe('viewport'); + expect(resolveGameShellKind('/game/sect/archive/methods')).toBe('viewport'); + expect(resolveGameShellKind('/game/sect/enlightenment-cliff')).toBe('viewport'); + expect(resolveGameShellKind('/game/sect/alchemy')).toBe('viewport'); + expect(resolveGameShellKind('/game/sect/industries')).toBe('viewport'); expect(resolveGameShellKind('/game/cultivator/attributes')).toBe( 'viewport', ); @@ -59,6 +64,15 @@ describe('game shell registry', () => { it('registers the sect abilities route without replacing creation skills', () => { const source = readFileSync('src/react-app/router.tsx', 'utf8'); expect(source).toContain('path="sect/abilities"'); + expect(source).toContain('path="sect/archive/abilities"'); + expect(source).toContain('path="sect/arena"'); + expect(source).toContain('path="sect/enlightenment-cliff"'); + expect(source).toContain('path="sect/alchemy"'); + expect(source).toContain('path="sect/refinery"'); + expect(source).toContain('path="sect/spirit-vein"'); + expect(source).toContain('path="sect/herb-garden"'); + expect(source).toContain('path="sect/affairs"'); + expect(source).toContain('path="sect/industries"'); expect(source).toContain("id: 'sect-abilities'"); expect(source).toContain("path=\"skills\""); }); diff --git a/src/react-app/lib/sect/sectClient.ts b/src/react-app/lib/sect/sectClient.ts index cdddd03b..098092b5 100644 --- a/src/react-app/lib/sect/sectClient.ts +++ b/src/react-app/lib/sect/sectClient.ts @@ -4,6 +4,11 @@ import type { SectCurrentData, SectDetailData, SectExperienceResponse, + SectConstructionData, + SectMembersData, + SectOverviewData, + SectShopData, + SectTasksData, } from '@shared/contracts/sect'; type SectExperienceData = SectExperienceResponse['data']; @@ -25,6 +30,27 @@ export function fetchSectCurrent(): Promise { return fetchData('/api/sects/current'); } +export function fetchSectOverview(): Promise { + return fetchData('/api/sects/current/overview'); +} + +export function fetchSectTasks(): Promise { + return fetchData('/api/sects/current/tasks'); +} + +export function fetchSectShop(): Promise { + return fetchData('/api/sects/current/shop'); +} + +export function fetchSectConstruction(): Promise { + return fetchData('/api/sects/current/construction'); +} + +export function fetchSectMembers(page = 1, pageSize = 20): Promise { + const query = new URLSearchParams({ page: String(page), pageSize: String(pageSize) }); + return fetchData(`/api/sects/current/members?${query.toString()}`); +} + export function fetchSectDetail(sectId: string): Promise { return fetchData(`/api/sects/${encodeURIComponent(sectId)}`); } diff --git a/src/react-app/router.tsx b/src/react-app/router.tsx index 4bcf75ca..4e10ea51 100644 --- a/src/react-app/router.tsx +++ b/src/react-app/router.tsx @@ -330,18 +330,107 @@ export const router = createBrowserRouter( import('@app/routes/game/sect/abilities/route'), + () => import('@app/routes/game/sect/abilities/redirect'), )} handle={scene( { id: 'sect-abilities', presentation: 'workflow', - summary: - '查阅并配置凌霄剑宗神通,详情将按当前流派与参悟方案呈现。', + summary: '旧宗门神通入口将迁往演武台。', }, - '宗门神通', + '演武台', )} /> + import('@app/routes/game/sect/hall/route'))} + handle={scene({ id: 'sect-hall', presentation: 'archive', summary: '身份、晋升、周俸与同门名录归于宗门大殿。' }, '宗门大殿')} + /> + import('@app/routes/game/sect/affairs/route'))} + handle={scene({ id: 'sect-affairs', summary: '宗门日常、周常、悬赏和晋升试炼由执事堂统一发放。' }, '执事堂')} + /> + import('@app/routes/game/sect/archive/route'))} + handle={scene({ id: 'sect-archive', presentation: 'workflow', summary: '宗门心法依次归架,研习受境界、职阶与藏经阁等级共同约束。' }, '藏经阁')} + /> + import('@app/routes/game/sect/archive/methods/route'))} + handle={scene({ id: 'sect-archive', presentation: 'workflow', summary: '旧心法楼入口将归入藏经阁。' }, '藏经阁')} + /> + import('@app/routes/game/sect/archive/paths/route'))} + handle={scene({ id: 'sect-enlightenment-cliff', presentation: 'workflow', summary: '旧流派廊入口将迁往悟道崖。' }, '悟道崖')} + /> + import('@app/routes/game/sect/archive/abilities/route'))} + handle={scene({ id: 'sect-abilities', presentation: 'workflow', summary: '旧神通壁入口将迁往演武台。' }, '演武台')} + /> + import('@app/routes/game/sect/enlightenment-cliff/route'))} + handle={scene({ id: 'sect-enlightenment-cliff', presentation: 'workflow', summary: '在崖间剑痕中选择流派、参悟经脉并配置战术。' }, '悟道崖')} + /> + import('@app/routes/game/sect/arena/route'))} + handle={scene({ id: 'sect-abilities', presentation: 'workflow', summary: '在演武阵台配置宗门神通与自动战术。' }, '演武台')} + /> + import('@app/routes/game/sect/treasury/route'))} + handle={scene({ id: 'sect-treasury', presentation: 'service', summary: '按弟子职阶使用贡献兑换常驻与每周轮换物资。' }, '宗门宝库')} + /> + import('@app/routes/game/sect/industries/route'))} + handle={scene({ id: 'sect-industries', presentation: 'archive', summary: '全宗设施、长老工程与建设捐献记录归于百业院。' }, '百业院')} + /> + import('@app/routes/game/sect/cultivation-room/route'))} + handle={scene({ id: 'sect-cultivation-room', summary: '宗门聚灵阵为现有闭关结算提供修为加成。' }, '宗门修炼室')} + /> + import('@app/routes/game/sect/workshop/route'))} + handle={scene({ id: 'sect', presentation: 'hub', summary: '旧丹器坊入口将返回宗门总视图。' }, '宗门')} + /> + import('@app/routes/game/sect/alchemy/route'))} + handle={scene({ id: 'sect-alchemy', presentation: 'workflow', summary: '借宗门丹火完成即兴炼丹与丹方炼制。' }, '宗门丹房')} + /> + import('@app/routes/game/sect/refinery/route'))} + handle={scene({ id: 'sect-refinery', presentation: 'workflow', summary: '借宗门地火锻造法宝。' }, '宗门器坊')} + /> + import('@app/routes/game/sect/spirit-vein/route'))} + handle={scene({ id: 'sect-spirit-vein', presentation: 'service', summary: '查看灵脉矿场等级、灵石俸禄加成与当前脉象。' }, '灵脉矿场')} + /> + import('@app/routes/game/sect/herb-garden/route'))} + handle={scene({ id: 'sect-herb-garden', presentation: 'service', summary: '查看药田等级、每周灵草产出与灵植长势。' }, '宗门药田')} + /> + import('@app/routes/game/sect/cave/route'))} + handle={scene({ id: 'sect-cave', summary: '内门弟子在宗门中的静态私人洞府。' }, '私人洞府')} + /> + import('@app/routes/game/sect/gate/route'))} + handle={scene({ id: 'sect-gate', presentation: 'service', summary: '宗门动态与未来拜师入口归于山门。' }, '宗门山门')} + /> ['compoundTier'], -): string { - switch (tier) { - case 'single': - return '单材'; - case 'balanced': - return '均衡'; - case 'synergy': - return '协同'; - case 'conflict': - return '冲突'; - } -} - -export function AlchemyGuideModal({ - isOpen, - onClose, -}: { - isOpen: boolean; - onClose: () => void; -}) { - return ( - -
-
-
- 即兴炼丹 -
-
-

- 先挑灵材,再写丹意。你写的是想成什么丹,炉中灵材决定这股药力能往哪条路走:疗伤、回元、积修、启悟、破境、清心、护脉、延寿、炼体、洗髓,都会从这里分出路数。 -

-

- 丹意写得越清楚,炉火越容易顺着你的意思走;灵材本性若不相合,也会把丹势带偏。想炼筑基丹,可投些温稳灵草或辅材,再写“冲关蓄势、辅助筑基、破境凝神”。 -

-

- 想推动肉身炼体时,丹意要写清皮肤、筋骨、脏腑、气血或元神等方向。成丹名称仍随材料与炉意生成,不再固定叫某一种炼体丹。 -

-

- 偶有一炉火候极顺、药路分明,出丹时便可能悟出丹方。留下丹方后,下次就能按方复炼。 -

-
-
- -
-
- 丹方炼制 -
-
-

- 丹方记的是成丹路数,不是死记哪几味药。先选丹方,再把你手头相近的灵材投入炉中;若炉位、品阶或灵石不够,炉前会直接拦下。 -

-

- 投料后先“推演药路”。这一步会看每味材料是能当主材、只能凑用,还是会拖偏丹方。 -

-

- 药路若见“契合”,便可安心开炉;若只是“勉强”或“偏路”,也能强行成丹,只是药力、品相和熟练收益都会受影响。 -

-
-
- -
-
- 炉火提醒 -
-
-

- 一炉里药味越多、方向越杂,火势越难稳。炉势不稳时即便成丹,疗伤、修为、感悟、炼体、延寿等药力也会打折。 -

-

- 猛药常带丹毒,燥烈材料用得多,服后更要记得调息。解毒一类丹药能化浊,但这一炉的主要药力也会分去一部分。 -

-

- 修为、启悟、破境、清心、护脉、延寿、炼体、洗髓都不是一口吞尽的便宜事。同一炉最好只求一条主路,丹意写得太散,反而什么都留不深。 -

-

- 破境丹服下后,可能留下破境凝神、清心或护脉之效。破境前看的是你身上有没有这份准备,不问这枚丹是不是亲手炼成。 -

-

- 肉身进阶另走炼体体系:看丹药是否为炼体类、药性是否对应目标五轨、丹药质量是否达标,不按丹药名称判断。 -

-
-
-
-
- ); -} - -export function FormulaNarrativeBlock({ - formula, - showMasteryExp = false, -}: { - formula: AlchemyFormula; - showMasteryExp?: boolean; -}) { - return ( -
-
{formula.description}
-
药路取向:{formatFormulaTags(formula)}
-
- 需 {formula.pattern.slotCount} 味灵材 - {formula.pattern.minQuality - ? `,至少 ${formula.pattern.minQuality}` - : ''} - {showMasteryExp ? `,熟练进度 ${formula.mastery.exp}` : ''} -
-
- ); -} - -export function AlchemyFormulaSummaryCard({ - formula, -}: { - formula: AlchemyFormula; -}) { - return ( - -
-
- {formula.name} - - {getPillFamilyLabel(formula.family)} - - - {`熟练 Lv.${formula.mastery.level}`} - -
- -
-
- ); -} - -export function AlchemyFormulaAnalysisModal({ - analysis, - cooldownRemainingSeconds, - isOpen, - isCrafting, - onClose, - onCraft, -}: { - analysis: FormulaAnalysisResult | null; - cooldownRemainingSeconds: number; - isOpen: boolean; - isCrafting: boolean; - onClose: () => void; - onCraft: () => void; -}) { - if (!analysis) { - return null; - } - - return ( - - - 稍后开炉 - - - {isCrafting ? '丹火炼中……' : '确认开炉'} - - - } - > -
-
- - {getFormulaFitBandLabel(analysis.fitBand)} - - - 合方程度 {Math.round(analysis.fitScore * 100)}% - -
- {analysis.batchProfile ? ( -
-
-
预计成丹
-
- {analysis.batchProfile.yieldQuantity} 枚 -
-
-
-
配伍
-
- {getBatchTierLabel(analysis.batchProfile.compoundTier)} -
-
- {analysis.batchProfile.roleSummary} -
-
-
- ) : null} -
-

{getFormulaAnalysisNarrative(analysis.fitBand)}

-

{getFormulaFitBandEffectText(analysis.fitBand)}

- {cooldownRemainingSeconds > 0 ? ( -

- 本次推演已留炉路,可直接开炉;{cooldownRemainingSeconds}{' '} - 秒后可重新推演药路。 -

- ) : null} -
- {analysis.materialJudgments.length > 0 ? ( -
- {analysis.materialJudgments.map((judgment) => ( -
- - {judgment.verdict === 'core' - ? '主材' - : judgment.verdict === 'usable' - ? '可用' - : '偏路'} - - {judgment.materialName} - {judgment.reason} -
- ))} -
- ) : null} - {analysis.warnings.length > 0 ? ( -
- {analysis.warnings.map((warning) => ( - - {warning} - - ))} -
- ) : null} -
-
- ); -} - -export function AlchemyFormulaListItem({ - formula, - isActive, - isDeleting, - onSelect, - onDelete, -}: { - formula: AlchemyFormula; - isActive: boolean; - isDeleting: boolean; - onSelect: () => void; - onDelete: () => void; -}) { - const requiredMasteryExp = getFormulaMasteryRequiredExp(formula); - - return ( -
{ - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault(); - onSelect(); - } - }} - className={cn( - 'w-full border px-3 py-3 text-left transition-colors', - isActive - ? 'border-crimson bg-crimson/5' - : 'border-ink/10 hover:border-ink/30', - )} - > -
-
-
- {formula.name} - - {getPillFamilyLabel(formula.family)} - -
-
- {`熟练 Lv.${formula.mastery.level}`} - {`进度 ${formula.mastery.exp}/${requiredMasteryExp}`} - {`需 ${formula.pattern.slotCount} 味灵材`} - {formula.pattern.minQuality ? ( - {`至少 ${formula.pattern.minQuality}`} - ) : null} -
-
-

{formula.description}

-

药路取向:{formatFormulaTags(formula)}

-
-
-
event.stopPropagation()} - onKeyDown={(event) => event.stopPropagation()} - > - - {isDeleting ? '删除中……' : '删除'} - -
-
-
- ); -} - -export function AlchemyFormulaSelectionModal({ - isOpen, - onClose, - formulas, - selectedFormulaId, - isLoading, - error, - search, - familyFilter, - pagination, - isDeleting, - isSubmitting, - onSearchChange, - onFamilyFilterChange, - onPageChange, - onSelectFormula, - onDeleteFormula, -}: { - isOpen: boolean; - onClose: () => void; - formulas: AlchemyFormula[]; - selectedFormulaId: string | null; - isLoading: boolean; - error: string | null; - search: string; - familyFilter: PillFamily | 'all'; - pagination: FormulaListPagination; - isDeleting: boolean; - isSubmitting: boolean; - onSearchChange: (value: string) => void; - onFamilyFilterChange: (value: PillFamily | 'all') => void; - onPageChange: (page: number) => void; - onSelectFormula: (formula: AlchemyFormula) => void; - onDeleteFormula: (formula: AlchemyFormula) => void; -}) { - return ( - -
- 共 {pagination.total} 份丹方,第 {pagination.page} /{' '} - {pagination.totalPages} 页 -
- - onPageChange(pagination.page - 1)} - disabled={ - isSubmitting || isLoading || !pagination.hasPreviousPage - } - > - 上一页 - - onPageChange(pagination.page + 1)} - disabled={isSubmitting || isLoading || !pagination.hasNextPage} - > - 下一页 - - - 完成选择 - - -
- } - > -
-
- - -
- - {error ? {error} : null} - {isLoading ? 正在整理你的丹方笔录…… : null} - {!isLoading && formulas.length === 0 ? ( - - {search || familyFilter !== 'all' - ? '没有符合筛选条件的丹方。' - : '你尚未悟得丹方。先在“即兴炼丹”中炼出稳固成丹,再尝试留方。'} - - ) : null} - -
- {formulas.map((formula) => ( - onSelectFormula(formula)} - onDelete={() => onDeleteFormula(formula)} - /> - ))} -
-
- - ); -} - -export function AlchemyResultModal({ - consumable, - formulaProgress, - isOpen, - onClose, - viewerRealm, -}: { - consumable: Consumable | null; - formulaProgress: FormulaProgress | null; - isOpen: boolean; - onClose: () => void; - viewerRealm?: RealmType; -}) { - if (!consumable || !isPillConsumable(consumable)) { - return null; - } - - const model = toPillDisplayModel(consumable, { realm: viewerRealm }); - const meta = consumable.spec.alchemyMeta; - - return ( - - ) : undefined - } - badges={[ - consumable.quality ? ( - - {consumable.type} - - ) : undefined, - - {getPillFamilyLabel(consumable.spec.family)} - , - meta.source === 'formula' ? ( - - 丹方炼制 - - ) : undefined, - ].filter(Boolean)} - metaSection={ -
-
- 本炉成丹 - - {consumable.quantity} 枚 - -
- {meta.batch ? ( -
- 配伍 - - {getBatchTierLabel(meta.batch.compoundTier)} ·{' '} - {meta.batch.roleSummary} - -
- ) : null} - - {formulaProgress && ( -
-
丹方熟练
-
-
本次熟练 +{formulaProgress.gainedExp}
-
- 当前等级 Lv.{formulaProgress.level},进度{' '} - {formulaProgress.exp} -
- {formulaProgress.leveledUp && ( -
- 丹方熟练提升:Lv.{formulaProgress.previousLevel} → Lv. - {formulaProgress.level} -
- )} -
-
- )} -
- } - description={consumable.description} - descriptionTitle="丹成评述" - /> - ); -} - -export function AlchemyFormulaDiscoveryModal({ - formulaDiscovery, - isHandlingDiscovery, - isOpen, - onAcceptDiscovery, - onRejectDiscovery, -}: { - formulaDiscovery: AlchemyFormulaDiscoveryCandidate | null; - isHandlingDiscovery: boolean; - isOpen: boolean; - onAcceptDiscovery: () => void; - onRejectDiscovery: () => void; -}) { - if (!formulaDiscovery) { - return null; - } - - return ( - undefined} - icon="📜" - name={formulaDiscovery.name} - badges={[ - - 新悟丹方 - , - - {getPillFamilyLabel(formulaDiscovery.family)} - , - ]} - metaSection={ -
- -
-
{formulaDiscovery.discoveryRemark}
-
- {formulaDiscovery.patternSummary} -
-
-
-
- } - description={formulaDiscovery.description} - descriptionTitle="留方记述" - footer={ - - - 暂不保存 - - - {isHandlingDiscovery ? '留方中……' : '保存丹方'} - - - } - /> - ); -} +import { AlchemyScene } from '@app/components/feature/craft/AlchemyScene'; export default function AlchemyPage() { - const { cultivator, note, isLoading } = usePlayerStateView(); - const { mutate } = usePlayerStateActions(); - const cultivatorId = cultivator?.id ?? null; - const { tasks } = useTaskList(cultivatorId ?? undefined); - const [activeMode, setActiveMode] = useState('improvised'); - const [selectedFormulaId, setSelectedFormulaId] = useState( - null, - ); - const [selectedMaterialIds, setSelectedMaterialIds] = useState([]); - const [selectedMaterialMap, setSelectedMaterialMap] = useState< - Record - >({}); - const [doseMap, setDoseMap] = useState>({}); - const [userPrompt, setUserPrompt] = useState(''); - const [status, setStatus] = useState(''); - const [isSubmitting, setSubmitting] = useState(false); - const [createdConsumable, setCreatedConsumable] = useState( - null, - ); - const [formulaDiscovery, setFormulaDiscovery] = - useState(null); - const [formulaProgress, setFormulaProgress] = - useState(null); - const [isResultModalOpen, setIsResultModalOpen] = useState(false); - const [isDiscoveryModalOpen, setIsDiscoveryModalOpen] = useState(false); - const [isHandlingDiscovery, setIsHandlingDiscovery] = useState(false); - const [isDeletingFormula, setIsDeletingFormula] = useState(false); - const [isGuideModalOpen, setIsGuideModalOpen] = useState(false); - const [isMaterialModalOpen, setIsMaterialModalOpen] = useState(false); - const [isFormulaSelectionModalOpen, setIsFormulaSelectionModalOpen] = - useState(false); - const [dialog, setDialog] = useState(null); - const [celebrationTick, setCelebrationTick] = useState(0); - const [materialsRefreshKey, setMaterialsRefreshKey] = useState(0); - const [previewState, setPreviewState] = useState( - DEFAULT_PREVIEW_STATE, - ); - const [formulaAnalysis, setFormulaAnalysis] = - useState(null); - const [isFormulaAnalysisModalOpen, setIsFormulaAnalysisModalOpen] = - useState(false); - const [isAnalyzingFormula, setIsAnalyzingFormula] = useState(false); - const [formulaAnalysisError, setFormulaAnalysisError] = useState< - string | null - >(null); - const [analysisCooldownRemaining, setAnalysisCooldownRemaining] = - useState(0); - const [analysisExpiresAfterMs, setAnalysisExpiresAfterMs] = useState( - null, - ); - const [formulas, setFormulas] = useState([]); - const [selectedFormulaSnapshot, setSelectedFormulaSnapshot] = - useState(null); - const [formulaSearch, setFormulaSearch] = useState(''); - const [formulaFamilyFilter, setFormulaFamilyFilter] = useState< - PillFamily | 'all' - >('all'); - const [formulaPage, setFormulaPage] = useState(1); - const [formulaPagination, setFormulaPagination] = - useState(DEFAULT_FORMULA_PAGINATION); - const [formulasError, setFormulasError] = useState(null); - const [isLoadingFormulas, setIsLoadingFormulas] = useState(false); - const analyzedFormulaSelectionKeyRef = useRef(null); - const { pushToast } = useInkUI(); - const { openQiActionConfirm } = useQiActionConfirm(); - const selectedFormula = useMemo( - () => - selectedFormulaSnapshot?.id === selectedFormulaId - ? selectedFormulaSnapshot - : (formulas.find((formula) => formula.id === selectedFormulaId) ?? null), - [formulas, selectedFormulaId, selectedFormulaSnapshot], - ); - const nextTutorialTask = useMemo(() => findNextTutorialTask(tasks), [tasks]); - const isStarterAlchemyTask = - nextTutorialTask?.definitionId === 'tutorial_first_alchemy'; - const formulaJudgmentMap = useMemo( - () => - Object.fromEntries( - (formulaAnalysis?.materialJudgments ?? []).map((judgment) => [ - judgment.materialId, - judgment, - ]), - ) as Record, - [formulaAnalysis], - ); - const currentFormulaSelectionKey = useMemo(() => { - if (activeMode !== 'formula' || !selectedFormulaId) { - return null; - } - - return JSON.stringify({ - formulaId: selectedFormulaId, - materials: selectedMaterialIds.map((id) => ({ - id, - dose: doseMap[id] ?? MIN_DOSE, - })), - }); - }, [activeMode, doseMap, selectedFormulaId, selectedMaterialIds]); - - useEffect(() => { - if (analysisCooldownRemaining <= 0) { - return; - } - - const timer = window.setInterval(() => { - setAnalysisCooldownRemaining((current) => Math.max(0, current - 1)); - }, 1000); - - return () => window.clearInterval(timer); - }, [analysisCooldownRemaining]); - - useEffect(() => { - if (!formulaAnalysis || !analysisExpiresAfterMs) { - return; - } - - const timer = window.setTimeout(() => { - setFormulaAnalysis(null); - setIsFormulaAnalysisModalOpen(false); - setAnalysisExpiresAfterMs(null); - setFormulaAnalysisError('上次推演已散去,请重新推演药路。'); - }, analysisExpiresAfterMs); - - return () => window.clearTimeout(timer); - }, [analysisExpiresAfterMs, formulaAnalysis]); - - const clearFormulaAnalysis = (options?: { keepError?: boolean }) => { - setFormulaAnalysis(null); - setIsFormulaAnalysisModalOpen(false); - setAnalysisExpiresAfterMs(null); - analyzedFormulaSelectionKeyRef.current = null; - if (!options?.keepError) { - setFormulaAnalysisError(null); - } - }; - - useEffect(() => { - if ( - analyzedFormulaSelectionKeyRef.current && - analyzedFormulaSelectionKeyRef.current !== currentFormulaSelectionKey - ) { - analyzedFormulaSelectionKeyRef.current = null; - setFormulaAnalysis(null); - setIsFormulaAnalysisModalOpen(false); - setAnalysisExpiresAfterMs(null); - setFormulaAnalysisError(null); - } - }, [currentFormulaSelectionKey]); - - const loadFormulas = async (options?: { - page?: number; - }) => { - if (!cultivatorId) { - return; - } - - try { - setIsLoadingFormulas(true); - const nextPage = options?.page ?? formulaPage; - const params = new URLSearchParams({ - page: String(nextPage), - pageSize: '5', - }); - const keyword = formulaSearch.trim(); - if (keyword) { - params.set('search', keyword); - } - if (formulaFamilyFilter !== 'all') { - params.set('family', formulaFamilyFilter); - } - const response = await fetch(`/api/alchemy/formulas?${params.toString()}`); - const result: FormulaListResponse = await response.json(); - - if (!response.ok || !result.success || !result.data) { - throw new Error(result.error || '丹方列表读取失败'); - } - - const nextFormulas = result.data.formulas; - - setFormulas(nextFormulas); - setFormulaPagination(result.data.pagination); - setFormulaPage(result.data.pagination.page); - setFormulasError(null); - } catch (error) { - setFormulasError( - error instanceof Error - ? error.message - : '丹方列表读取失败,请稍后再试。', - ); - } finally { - setIsLoadingFormulas(false); - } - }; - - useEffect(() => { - if (!cultivatorId || !isFormulaSelectionModalOpen) { - return; - } - - let cancelled = false; - - const loadInitialFormulas = async () => { - try { - setIsLoadingFormulas(true); - const params = new URLSearchParams({ - page: String(formulaPage), - pageSize: '5', - }); - const keyword = formulaSearch.trim(); - if (keyword) { - params.set('search', keyword); - } - if (formulaFamilyFilter !== 'all') { - params.set('family', formulaFamilyFilter); - } - const response = await fetch(`/api/alchemy/formulas?${params.toString()}`); - const result: FormulaListResponse = await response.json(); - - if (cancelled) return; - - if (!response.ok || !result.success || !result.data) { - throw new Error(result.error || '丹方列表读取失败'); - } - - const nextFormulas = result.data.formulas; - - setFormulas(nextFormulas); - setFormulaPagination(result.data.pagination); - setFormulaPage(result.data.pagination.page); - setFormulasError(null); - } catch (error) { - if (cancelled) return; - setFormulasError( - error instanceof Error - ? error.message - : '丹方列表读取失败,请稍后再试。', - ); - } finally { - if (!cancelled) { - setIsLoadingFormulas(false); - } - } - }; - - void loadInitialFormulas(); - - return () => { - cancelled = true; - }; - }, [ - cultivatorId, - formulaFamilyFilter, - formulaPage, - formulaSearch, - isFormulaSelectionModalOpen, - ]); - - const previewRequest = useMemo(() => { - if (selectedMaterialIds.length === 0) { - return null; - } - if (activeMode === 'formula' && !selectedFormulaId) { - return null; - } - - const params = new URLSearchParams({ - craftType: CRAFT_TYPE, - alchemyMode: activeMode, - materialIds: selectedMaterialIds.join(','), - }); - const materialQuantities = Object.fromEntries( - selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), - ); - params.set('materialQuantities', JSON.stringify(materialQuantities)); - if (activeMode === 'formula' && selectedFormulaId) { - params.set('formulaId', selectedFormulaId); - } - - return { - key: JSON.stringify({ - activeMode, - selectedFormulaId, - materialIds: selectedMaterialIds, - materialQuantities, - }), - url: `/api/craft?${params.toString()}`, - }; - }, [activeMode, doseMap, selectedFormulaId, selectedMaterialIds]); - - useEffect(() => { - if (!previewRequest) { - return; - } - - let cancelled = false; - - const loadPreview = async () => { - try { - const response = await fetch(previewRequest.url); - const result: AlchemyPreviewResponse = await response.json(); - - if (cancelled) return; - - if (!response.ok || !result.success || !result.data) { - throw new Error(result.error || '炼丹预估失败'); - } - - setPreviewState({ - key: previewRequest.key, - estimatedSpiritStones: result.data.cost.spiritStones, - validation: result.data.validation, - batchPreview: result.data.batchPreview ?? null, - canAfford: result.data.canAfford, - previewError: null, - }); - } catch (error) { - if (cancelled) return; - setPreviewState({ - key: previewRequest.key, - estimatedSpiritStones: null, - validation: null, - batchPreview: null, - canAfford: true, - previewError: - error instanceof Error - ? error.message - : '炼丹预估失败,请稍后再试。', - }); - } - }; - - void loadPreview(); - - return () => { - cancelled = true; - }; - }, [previewRequest]); - - const resetWorkbench = () => { - setStatus(''); - setCreatedConsumable(null); - setFormulaDiscovery(null); - setFormulaProgress(null); - setIsResultModalOpen(false); - setIsDiscoveryModalOpen(false); - setSelectedMaterialIds([]); - setSelectedMaterialMap({}); - setDoseMap({}); - setUserPrompt(''); - setPreviewState(DEFAULT_PREVIEW_STATE); - clearFormulaAnalysis(); - setIsMaterialModalOpen(false); - }; - - const handleModeChange = (value: string) => { - const nextMode = value as AlchemyMode; - if (nextMode === activeMode) { - return; - } - - setActiveMode(nextMode); - resetWorkbench(); - }; - - const toggleMaterial = (id: string, material?: Material) => { - clearFormulaAnalysis(); - setSelectedMaterialIds((prev) => { - if (prev.includes(id)) { - setSelectedMaterialMap((map) => { - const next = { ...map }; - delete next[id]; - return next; - }); - setDoseMap((map) => { - const next = { ...map }; - delete next[id]; - return next; - }); - return prev.filter((mid) => mid !== id); - } - if (prev.length >= MAX_MATERIALS) { - pushToast({ - message: `丹炉承载有限,最多投入 ${MAX_MATERIALS} 种灵材`, - tone: 'warning', - }); - return prev; - } - if (material) { - setSelectedMaterialMap((map) => ({ ...map, [id]: material })); - setDoseMap((map) => ({ ...map, [id]: MIN_DOSE })); - } - return [...prev, id]; - }); - }; - - const handleDoseChange = (id: string, dose: number) => { - const material = selectedMaterialMap[id]; - if (!material) return; - clearFormulaAnalysis(); - const stock = material.quantity ?? 0; - const clamped = Math.min( - Math.min(MAX_DOSE, Math.max(stock, MIN_DOSE)), - Math.max(MIN_DOSE, Math.floor(dose)), - ); - setDoseMap((prev) => ({ ...prev, [id]: clamped })); - }; - - const resetAll = () => { - resetWorkbench(); - }; - - const submitPayload = useMemo( - () => ({ - materialIds: selectedMaterialIds, - craftType: CRAFT_TYPE, - alchemyMode: activeMode, - formulaId: activeMode === 'formula' ? selectedFormulaId : undefined, - analysisId: - activeMode === 'formula' ? formulaAnalysis?.analysisId : undefined, - materialQuantities: Object.fromEntries( - selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), - ), - userPrompt: activeMode === 'improvised' ? userPrompt.trim() : undefined, - }), - [ - activeMode, - doseMap, - formulaAnalysis?.analysisId, - selectedFormulaId, - selectedMaterialIds, - userPrompt, - ], - ); - - const hasFreshPreview = previewState.key === previewRequest?.key; - const estimatedSpiritStones = hasFreshPreview - ? previewState.estimatedSpiritStones - : null; - const batchPreview = hasFreshPreview ? previewState.batchPreview : null; - const validation = hasFreshPreview ? previewState.validation : null; - const canAfford = hasFreshPreview ? previewState.canAfford : true; - const previewError = hasFreshPreview ? previewState.previewError : null; - const displayValidation = validation; - const displayCanAfford = canAfford; - const isFormulaMode = activeMode === 'formula'; - const displayBatchPreview = isFormulaMode ? batchPreview : null; - const displayPreviewWarnings = - isFormulaMode ? (displayValidation?.warnings ?? []) : []; - const qiCost = isFormulaMode - ? QI_ACTION_COSTS.alchemy_formula - : QI_ACTION_COSTS.alchemy_improvised; - const hasCraftableFormulaAnalysis = - !!formulaAnalysis?.analysisId; - const canAnalyzeFormula = - !isSubmitting && - !isAnalyzingFormula && - !!selectedFormulaId && - selectedMaterialIds.length > 0 && - hasFreshPreview && - estimatedSpiritStones !== null && - !previewError && - displayCanAfford && - displayValidation?.valid !== false && - analysisCooldownRemaining <= 0; - const canCraftFormula = - !isSubmitting && - !!selectedFormulaId && - hasCraftableFormulaAnalysis && - !previewError && - estimatedSpiritStones !== null && - displayCanAfford && - displayValidation?.valid !== false; - const canChooseMaterials = !isSubmitting && (!isFormulaMode || !!selectedFormulaId); - const formulaPrimaryButtonLabel = isSubmitting - ? '丹火炼中……' - : isAnalyzingFormula - ? '推演中……' - : !formulaAnalysis?.analysisId - ? analysisCooldownRemaining > 0 - ? `${analysisCooldownRemaining} 秒后可再推演` - : '推演药路' - : formulaAnalysis.fitBand === 'poor' - ? '强行开炉' - : '依方成丹'; - - const handleAnalyzeFormula = async () => { - if (!cultivator) { - pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); - return; - } - if (!selectedFormulaId) { - pushToast({ message: '请先选定丹方。', tone: 'warning' }); - return; - } - if (selectedMaterialIds.length === 0) { - pushToast({ message: '请先投入灵材。', tone: 'warning' }); - return; - } - if (!hasFreshPreview || estimatedSpiritStones === null) { - pushToast({ message: '炉前验材尚未完成。', tone: 'warning' }); - return; - } - if (previewError || displayValidation?.valid === false || !displayCanAfford) { - pushToast({ message: '请先让这一炉通过验材。', tone: 'warning' }); - return; - } - if (analysisCooldownRemaining > 0) { - pushToast({ - message: `炉意未散,请 ${analysisCooldownRemaining} 秒后再推演药路。`, - tone: 'warning', - }); - return; - } - - setIsAnalyzingFormula(true); - setFormulaAnalysisError(null); - - try { - const response = await fetch( - `/api/alchemy/formulas/${selectedFormulaId}/analyze`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - materialIds: selectedMaterialIds, - materialQuantities: Object.fromEntries( - selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), - ), - }), - }, - ); - const result: FormulaAnalyzeResponse = await response.json(); - - if (!response.ok || !result.success || !result.data) { - if (typeof result.remainingSeconds === 'number') { - setAnalysisCooldownRemaining(result.remainingSeconds); - } - throw new Error(result.error || '推演药路失败'); - } - - if (!result.data.valid) { - clearFormulaAnalysis(); - setFormulaAnalysisError(result.data.staticBlockingReason || '当前炉材未通过炉前验材。'); - return; - } - - setFormulaAnalysis(result.data); - setIsFormulaAnalysisModalOpen(true); - setAnalysisExpiresAfterMs(result.data.expiresInSeconds * 1000); - analyzedFormulaSelectionKeyRef.current = currentFormulaSelectionKey; - setAnalysisCooldownRemaining(result.data.cooldownRemainingSeconds); - pushToast({ - message: - result.data.fitBand === 'aligned' - ? '药路已明,可依方成丹。' - : result.data.fitBand === 'degraded' - ? '这炉尚可循方,但药力会有折损。' - : '这炉偏离丹方较远,仍可强行收丹。', - tone: - result.data.fitBand === 'poor' - ? 'warning' - : result.data.fitBand === 'degraded' - ? 'default' - : 'success', - }); - } catch (error) { - clearFormulaAnalysis({ keepError: true }); - setFormulaAnalysisError( - error instanceof Error ? error.message : '推演药路失败,请稍后再试。', - ); - pushToast({ - message: - error instanceof Error ? error.message : '推演药路失败,请稍后再试。', - tone: 'danger', - }); - } finally { - setIsAnalyzingFormula(false); - } - }; - - const handleSubmit = async () => { - if (!cultivator) { - pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); - return; - } - if (activeMode === 'formula' && !selectedFormulaId) { - pushToast({ message: '请先选定丹方。', tone: 'warning' }); - return; - } - if (activeMode === 'formula' && !formulaAnalysis?.analysisId) { - pushToast({ message: '请先推演药路。', tone: 'warning' }); - return; - } - if (selectedMaterialIds.length === 0) { - pushToast({ message: '丹炉已备,只欠灵材。', tone: 'warning' }); - return; - } - if (activeMode === 'improvised' && !userPrompt.trim()) { - pushToast({ message: '请先注入丹意。', tone: 'warning' }); - return; - } - if (previewError || validation?.valid === false || !displayCanAfford) { - pushToast({ message: '当前炉况未稳,暂不可开炉。', tone: 'warning' }); - return; - } - openQiActionConfirm({ - actionName: activeMode === 'formula' ? '丹方炼丹' : '开炉炼丹', - qiCost, - confirmLabel: activeMode === 'formula' ? '依方成丹' : '开炉炼丹', - onConfirm: async () => { - setSubmitting(true); - setStatus( - activeMode === 'formula' - ? '丹方引火,炉势循脉而行……' - : '地火回环,药性相搏……', - ); - setCreatedConsumable(null); - setFormulaDiscovery(null); - setFormulaProgress(null); - setIsResultModalOpen(false); - setIsDiscoveryModalOpen(false); - - try { - const result = await mutate>( - fetch('/api/craft', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(submitPayload), - }), - ); - - if (!result.consumable) { - throw new Error('炼丹失败'); - } - - const nextConsumable = result.consumable; - const discoveredFormula = result.formulaDiscovery ?? null; - const successMessage = `【${nextConsumable.name}】丹成 ${nextConsumable.quantity} 枚!`; - setCreatedConsumable(nextConsumable); - setFormulaDiscovery(discoveredFormula); - setFormulaProgress(result.formulaProgress ?? null); - setIsResultModalOpen(true); - setIsDiscoveryModalOpen(false); - setCelebrationTick((prev) => prev + 1); - setStatus(successMessage); - pushToast({ message: successMessage, tone: 'success' }); - setSelectedMaterialIds([]); - setSelectedMaterialMap({}); - setDoseMap({}); - setIsMaterialModalOpen(false); - if (activeMode === 'improvised') { - setUserPrompt(''); - } - setPreviewState(DEFAULT_PREVIEW_STATE); - clearFormulaAnalysis(); - setMaterialsRefreshKey((prev) => prev + 1); - } catch (error) { - if (error instanceof Error && error.message.includes('请先推演药路')) { - clearFormulaAnalysis({ keepError: true }); - setFormulaAnalysisError(error.message); - } - const failMessage = - error instanceof Error - ? `炸炉了:${error.message}` - : '炼丹失败,请稍后再试。'; - setStatus(failMessage); - pushToast({ message: failMessage, tone: 'danger' }); - } finally { - setSubmitting(false); - } - }, - }); - }; - - const handleDiscoveryDecision = async (accept: boolean) => { - if (!formulaDiscovery) { - return; - } - - setIsHandlingDiscovery(true); - try { - const response = await fetch('/api/alchemy/formulas/discovery/confirm', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - token: formulaDiscovery.token, - accept, - }), - }); - const result: DiscoveryConfirmResponse = await response.json(); - - if (!response.ok || !result.success || !result.data) { - throw new Error(result.error || '丹方确认失败'); - } - - if (accept && result.data.saved && result.data.formula) { - setSelectedFormulaId(result.data.formula.id); - setSelectedFormulaSnapshot(result.data.formula); - setFormulaPage(1); - await loadFormulas({ page: 1 }); - pushToast({ - message: `已悟得【${result.data.formula.name}】`, - tone: 'success', - }); - } else if (!accept) { - pushToast({ - message: '丹意散去,未留成方。', - tone: 'default', - }); - } - - setFormulaDiscovery(null); - setIsDiscoveryModalOpen(false); - } catch (error) { - pushToast({ - message: - error instanceof Error ? error.message : '丹方确认失败,请稍后再试。', - tone: 'danger', - }); - } finally { - setIsHandlingDiscovery(false); - } - }; - - const openDeleteFormulaConfirm = (formula: AlchemyFormula) => { - setDialog({ - id: `delete-formula-${formula.id}`, - title: '删除丹方', - content: ( -
-

- 确定要删去{' '} - {formula.name}{' '} - 吗? -

-

- 删除后将无法恢复,但已炼成丹药的来源记述不会受影响。 -

-
- ), - confirmLabel: '删除丹方', - cancelLabel: '作罢', - loading: isDeletingFormula, - loadingLabel: '删除中……', - onConfirm: async () => { - if (isDeletingFormula) { - return; - } - - try { - setIsDeletingFormula(true); - setDialog((currentDialog) => - currentDialog - ? { - ...currentDialog, - loading: true, - } - : currentDialog, - ); - - const response = await fetch(`/api/alchemy/formulas/${formula.id}`, { - method: 'DELETE', - }); - const result: FormulaDeleteResponse = await response.json(); - - if (!response.ok || !result.success) { - throw new Error(result.error || '丹方删除失败'); - } - - setPreviewState(DEFAULT_PREVIEW_STATE); - clearFormulaAnalysis(); - if (selectedFormulaId === formula.id) { - setSelectedFormulaId(null); - setSelectedFormulaSnapshot(null); - } - await loadFormulas({ - page: - formulas.length === 1 && formulaPagination.hasPreviousPage - ? formulaPagination.page - 1 - : formulaPagination.page, - }); - pushToast({ - message: result.message || `已删除【${formula.name}】`, - tone: 'success', - }); - } catch (error) { - pushToast({ - message: - error instanceof Error - ? error.message - : '丹方删除失败,请稍后再试。', - tone: 'danger', - }); - } finally { - setIsDeletingFormula(false); - setDialog((currentDialog) => - currentDialog - ? { - ...currentDialog, - loading: false, - } - : currentDialog, - ); - } - }, - }); - }; - - if (isLoading && !cultivator) { - return ( -
-

丹火温养中……

-
- ); - } - - const headerStatus = - activeMode === 'formula' && selectedFormula - ? `已选丹方:${selectedFormula.name}` - : selectedMaterialIds.length > 0 - ? `已投入 ${selectedMaterialIds.length} 种灵材` - : activeMode === 'formula' - ? '请先选定丹方,再投入灵材。' - : '请投入灵材并注入丹意。'; - const handlePrimaryAction = () => { - if (isFormulaMode) { - if (hasCraftableFormulaAnalysis) { - void handleSubmit(); - return; - } - void handleAnalyzeFormula(); - return; - } - - void handleSubmit(); - }; - - return ( - - {note ? ( - -

{note}

-
- ) : null} -
-

- {headerStatus} -

- setIsGuideModalOpen(true)} - className="shrink-0" - > - 炉理指引 - -
- - } - aside={ - <> - -
-

先选青露草、凝水花一类凡品灵草,投入 1 份即可。

-

丹意可写:疗伤回元,药性温和。

-

第一炉只求看懂成丹结果,不必追求高品阶。

- {isStarterAlchemyTask ? ( - { - setActiveMode('improvised'); - setUserPrompt(STARTER_ALCHEMY_PROMPT); - setPreviewState(DEFAULT_PREVIEW_STATE); - clearFormulaAnalysis(); - setIsMaterialModalOpen(true); - }} - disabled={isSubmitting} - > - 选择首炉灵材 - - ) : null} -
-
- - -
-

- 当前炉法:{activeMode === 'formula' ? '丹方炼制' : '即兴炼丹'} -

-

- 已投入灵材:{selectedMaterialIds.length}/{MAX_MATERIALS} -

-

灵石余额:{cultivator?.spirit_stones ?? 0}

- {estimatedSpiritStones !== null ? ( -

预计耗费:{estimatedSpiritStones} 灵石

- ) : null} - {displayBatchPreview ? ( -

- 预计出丹:{displayBatchPreview.minYield} - {displayBatchPreview.maxYield !== displayBatchPreview.minYield - ? `-${displayBatchPreview.maxYield}` - : ''}{' '} - 枚 -

- ) : null} -
-
- - - {activeMode === 'formula' && selectedFormula ? ( -
-

已选丹方:{selectedFormula.name}

-

丹方族类:{getPillFamilyLabel(selectedFormula.family)}

-

熟练等级:Lv.{selectedFormula.mastery.level}

-

- {formulaAnalysis - ? getFormulaAnalysisNarrative(formulaAnalysis.fitBand) - : '丹方已定,只待这一炉药性显出真章。'} -

-
- ) : activeMode === 'formula' ? ( -

请先选定丹方,再投入灵材。

- ) : ( -

请投入灵材并注入丹意,炉火会顺着材料药性与这缕心意凝成丹形。

- )} - {previewError ? ( -

{previewError}

- ) : null} - {displayValidation?.blockingReason ? ( -

- {displayValidation.blockingReason} -

- ) : null} -
- - } - > - - - {activeMode === 'formula' ? ( - - {selectedFormula ? ( -
- - setIsFormulaSelectionModalOpen(true)} - disabled={isSubmitting} - > - 更换丹方 - - {formulaAnalysis ? ( - -
- - 药路已推演:{getFormulaFitBandLabel(formulaAnalysis.fitBand)} - ,合方程度 {Math.round(formulaAnalysis.fitScore * 100)}%。 - - {formulaAnalysis.batchProfile ? ( - - 本炉预计 {formulaAnalysis.batchProfile.yieldQuantity}{' '} - 枚,配伍 - {getBatchTierLabel( - formulaAnalysis.batchProfile.compoundTier, - )} - 。 - - ) : null} - setIsFormulaAnalysisModalOpen(true)} - disabled={isSubmitting} - > - 查看点评 - -
-
- ) : formulaAnalysisError ? ( - {formulaAnalysisError} - ) : selectedMaterialIds.length > 0 ? ( - - 灵材既入炉中,还需先推演药路,再可依方收丹。 - - ) : null} -
- ) : ( -
- - 先选定一份丹方,再安排炉材。 - - setIsFormulaSelectionModalOpen(true)} - disabled={isSubmitting} - > - 选择丹方 - -
- )} -
- ) : null} - - -
-

- 已投入 {selectedMaterialIds.length} / {MAX_MATERIALS} 种灵材 -

- setIsMaterialModalOpen(true)} - disabled={!canChooseMaterials} - > - 打开材料 - -
- {isFormulaMode && !selectedFormulaId ? ( - - 先选丹方,再甄选这一炉要投入的灵材。 - - ) : null} - toggleMaterial(id)} - onDoseChange={handleDoseChange} - /> -
- - {activeMode === 'improvised' ? ( - - - - ) : null} - - - {estimatedSpiritStones !== null ? ( -
- - {SPIRIT_STONES_LABEL}: - - {estimatedSpiritStones} - {' '} - 枚 - - - {displayCanAfford ? '✓ 资源充足' : '✗ 灵石不足'} - -
- ) : ( - - {activeMode === 'formula' - ? '请先选定丹方并投入材料,过一遍炉前验材。' - : '请先选择材料以查看本炉消耗。'} - - )} - - {displayBatchPreview ? ( - - {displayBatchPreview.summary} 预计出丹{' '} - {displayBatchPreview.minYield} - {displayBatchPreview.maxYield !== displayBatchPreview.minYield - ? `-${displayBatchPreview.maxYield}` - : ''}{' '} - 枚;共 {displayBatchPreview.materialKindCount} 种灵材,合计{' '} - {displayBatchPreview.totalDose} 份。 - - ) : null} - - {previewError && {previewError}} - {displayValidation?.blockingReason && ( - - {displayValidation.blockingReason} - - )} - {displayPreviewWarnings.map((warning) => ( - - {warning} - - ))} -
- - - - 重置 - - - {isFormulaMode - ? formulaPrimaryButtonLabel - : isSubmitting - ? '丹火炼中……' - : '开炉炼丹'} - - - - {status && !isResultModalOpen && ( -
- {status} -
- )} - - setIsMaterialModalOpen(false)} - title="甄选炼丹灵材" - maxMaterials={MAX_MATERIALS} - cultivatorId={cultivator?.id} - selectedMaterialIds={selectedMaterialIds} - onToggleMaterial={toggleMaterial} - selectedMaterialMap={selectedMaterialMap} - isSubmitting={isSubmitting} - pageSize={20} - includeMaterialTypes={[...ALLOWED_MATERIAL_TYPES] as MaterialType[]} - refreshKey={materialsRefreshKey} - loadingText="正在检索储物袋中的灵材,请稍候……" - emptyNoticeText="暂无可用于炼丹的材料。" - totalText={(total) => `共 ${total} 份可用于炼丹的材料`} - /> - - setIsFormulaSelectionModalOpen(false)} - formulas={formulas} - selectedFormulaId={selectedFormulaId} - isLoading={isLoadingFormulas} - error={formulasError} - search={formulaSearch} - familyFilter={formulaFamilyFilter} - pagination={formulaPagination} - isDeleting={isDeletingFormula} - isSubmitting={isSubmitting} - onSearchChange={(value) => { - setFormulaSearch(value); - setFormulaPage(1); - }} - onFamilyFilterChange={(value) => { - setFormulaFamilyFilter(value); - setFormulaPage(1); - }} - onPageChange={setFormulaPage} - onSelectFormula={(formula) => { - clearFormulaAnalysis(); - setSelectedFormulaId(formula.id); - setSelectedFormulaSnapshot(formula); - setPreviewState(DEFAULT_PREVIEW_STATE); - setIsFormulaSelectionModalOpen(false); - }} - onDeleteFormula={openDeleteFormulaConfirm} - /> - - { - setIsResultModalOpen(false); - if (formulaDiscovery) { - setIsDiscoveryModalOpen(true); - } - }} - viewerRealm={cultivator?.realm} - /> - - void handleDiscoveryDecision(true)} - onRejectDiscovery={() => void handleDiscoveryDecision(false)} - /> - - setIsFormulaAnalysisModalOpen(false)} - onCraft={() => { - setIsFormulaAnalysisModalOpen(false); - void handleSubmit(); - }} - /> - - setIsGuideModalOpen(false)} - /> - - { - if (!isDeletingFormula) { - setDialog(null); - } - }} - /> - - {celebrationTick > 0 && ( - - )} -
- ); + return ; } diff --git a/src/react-app/routes/game/craft/refine/route.tsx b/src/react-app/routes/game/craft/refine/route.tsx index 7f8784ac..9d625185 100644 --- a/src/react-app/routes/game/craft/refine/route.tsx +++ b/src/react-app/routes/game/craft/refine/route.tsx @@ -1,413 +1,5 @@ -import { - CreationIntentPanel, - CreationProductResultModal, - MaterialSelectionModal, - SelectedMaterialsWithDose, - type CreationProductResultRecord, -} from '@app/components/feature/creation'; -import { - GameSceneAsideSection, - GameSceneFrame, - GameSceneNote, - GameSceneSection, -} from '@app/components/game-shell'; -import { useInkUI } from '@app/components/providers/InkUIProvider'; -import { - InkActionGroup, - InkButton, - InkIdentifyCelebration, - InkNotice, -} from '@app/components/ui'; -import { - useQiActionConfirm, -} from '@app/components/feature/cultivator/useQiActionConfirm'; -import { QI_ACTION_COSTS } from '@shared/config/qiSystem'; -import { CREATION_INPUT_CONSTRAINTS } from '@shared/engine/creation-v2/config/CreationBalance'; -import { getAllowedMaterialTypesForCraftType } from '@shared/engine/creation-v2/config/CreationCraftPolicy'; -import { usePlayerStateView } from '@app/lib/player-state/selectors'; -import { getGameConceptLabel } from '@shared/lib/gameConceptDisplay'; -import { usePlayerStateActions } from '@app/lib/player-state/store'; -import type { EquipmentSlot } from '@shared/types/constants'; -import type { Material } from '@shared/types/cultivator'; -import { useEffect, useMemo, useState } from 'react'; - -const CRAFT_TYPE = 'refine' as const; -const ALLOWED_MATERIAL_TYPES = [...getAllowedMaterialTypesForCraftType(CRAFT_TYPE)]; -const MAX_MATERIALS = CREATION_INPUT_CONSTRAINTS.maxMaterialKinds; -const MIN_DOSE = CREATION_INPUT_CONSTRAINTS.minQuantityPerMaterial; -const MAX_DOSE = CREATION_INPUT_CONSTRAINTS.maxQuantityPerMaterial; -const SPIRIT_STONES_LABEL = getGameConceptLabel('spirit_stones'); - -type CostEstimate = { - spiritStones?: number; - comprehension?: number; -}; - -type CostResponse = { - success: boolean; - data?: { - cost: CostEstimate; - canAfford: boolean; - validation: PreviewValidation | null; - }; -}; - -type PreviewValidation = { - valid: boolean; - blockingReason?: string; - warnings: string[]; - missingMatchingManual: boolean; -}; +import { RefineScene } from '@app/components/feature/craft/RefineScene'; export default function RefinePage() { - const { cultivator, note, isLoading } = usePlayerStateView(); - const { mutate } = usePlayerStateActions(); - const [selectedMaterialIds, setSelectedMaterialIds] = useState([]); - const [selectedMaterialMap, setSelectedMaterialMap] = useState< - Record - >({}); - const [doseMap, setDoseMap] = useState>({}); - const [userPrompt, setUserPrompt] = useState(''); - const [requestedSlot, setRequestedSlot] = useState(''); - const [status, setStatus] = useState(''); - const [isSubmitting, setSubmitting] = useState(false); - const [createdResult, setCreatedResult] = - useState(null); - const [isResultModalOpen, setIsResultModalOpen] = useState(false); - const [isMaterialModalOpen, setIsMaterialModalOpen] = useState(false); - const [celebrationTick, setCelebrationTick] = useState(0); - const [materialsRefreshKey, setMaterialsRefreshKey] = useState(0); - const [estimatedCost, setEstimatedCost] = useState(null); - const [validation, setValidation] = useState(null); - const [canAfford, setCanAfford] = useState(true); - const { pushToast } = useInkUI(); - const { openQiActionConfirm } = useQiActionConfirm(); - - useEffect(() => { - if (selectedMaterialIds.length === 0) { - return; - } - - let cancelled = false; - - const loadCostEstimate = async () => { - try { - const response = await fetch( - `/api/craft?craftType=${CRAFT_TYPE}&materialIds=${selectedMaterialIds.join(',')}`, - ); - const result: CostResponse = await response.json(); - if (!cancelled && result.success && result.data) { - setEstimatedCost(result.data.cost); - setCanAfford(result.data.canAfford); - setValidation(result.data.validation); - } - } catch (error) { - if (!cancelled) { - setValidation(null); - console.error('Failed to fetch cost estimate:', error); - } - } - }; - - void loadCostEstimate(); - - return () => { - cancelled = true; - }; - }, [selectedMaterialIds]); - - const toggleMaterial = (id: string, material?: Material) => { - setSelectedMaterialIds((prev) => { - if (prev.includes(id)) { - setSelectedMaterialMap((map) => { - const next = { ...map }; - delete next[id]; - return next; - }); - setDoseMap((map) => { - const next = { ...map }; - delete next[id]; - return next; - }); - return prev.filter((mid) => mid !== id); - } - if (prev.length >= MAX_MATERIALS) { - pushToast({ - message: `炼器炉量力有限,最多投入 ${MAX_MATERIALS} 种灵材`, - tone: 'warning', - }); - return prev; - } - if (material) { - setSelectedMaterialMap((map) => ({ ...map, [id]: material })); - setDoseMap((map) => ({ ...map, [id]: MIN_DOSE })); - } - return [...prev, id]; - }); - }; - - const handleDoseChange = (id: string, dose: number) => { - const material = selectedMaterialMap[id]; - if (!material) return; - const stock = material.quantity ?? 0; - const clamped = Math.min( - Math.min(MAX_DOSE, Math.max(stock, MIN_DOSE)), - Math.max(MIN_DOSE, Math.floor(dose)), - ); - setDoseMap((prev) => ({ ...prev, [id]: clamped })); - }; - - const resetAll = () => { - setStatus(''); - setCreatedResult(null); - setIsResultModalOpen(false); - setSelectedMaterialIds([]); - setSelectedMaterialMap({}); - setDoseMap({}); - setUserPrompt(''); - setRequestedSlot(''); - setValidation(null); - setIsMaterialModalOpen(false); - }; - - const submitPayload = useMemo( - () => ({ - materialIds: selectedMaterialIds, - craftType: CRAFT_TYPE, - materialQuantities: Object.fromEntries( - selectedMaterialIds.map((id) => [id, doseMap[id] ?? MIN_DOSE]), - ), - userPrompt: userPrompt.trim() || undefined, - requestedSlot: requestedSlot || undefined, - }), - [selectedMaterialIds, doseMap, userPrompt, requestedSlot], - ); - - const displayEstimatedCost = - selectedMaterialIds.length > 0 ? estimatedCost : null; - const displayValidation = selectedMaterialIds.length > 0 ? validation : null; - const displayCanAfford = selectedMaterialIds.length > 0 ? canAfford : true; - const qiCost = QI_ACTION_COSTS.creation_artifact; - - const handleSubmit = async () => { - if (!cultivator) { - pushToast({ message: '请先在首页觉醒灵根。', tone: 'warning' }); - return; - } - - if (selectedMaterialIds.length === 0) { - pushToast({ message: '巧妇难为无米之炊,请投入灵材。', tone: 'warning' }); - return; - } - - if (!requestedSlot) { - pushToast({ message: '请选择目标槽位以确定法宝类型。', tone: 'warning' }); - return; - } - - openQiActionConfirm({ - actionName: '开炉炼器', - qiCost, - confirmLabel: '开炉炼器', - onConfirm: async () => { - setSubmitting(true); - setStatus('炉火纯青,真火锤锻……'); - setCreatedResult(null); - setIsResultModalOpen(false); - - try { - const artifact = await mutate( - fetch('/api/craft', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(submitPayload), - }), - ); - const successMessage = `【${artifact.name}】出世!`; - setCreatedResult(artifact); - setIsResultModalOpen(true); - setCelebrationTick((prev) => prev + 1); - setStatus(successMessage); - pushToast({ message: successMessage, tone: 'success' }); - setSelectedMaterialIds([]); - setSelectedMaterialMap({}); - setDoseMap({}); - setIsMaterialModalOpen(false); - setMaterialsRefreshKey((prev) => prev + 1); - } catch (error) { - const failMessage = - error instanceof Error - ? `炸炉了:${error.message}` - : '炼制失败,请稍后再试。'; - setStatus(failMessage); - pushToast({ message: failMessage, tone: 'danger' }); - } finally { - setSubmitting(false); - } - }, - }); - }; - - if (isLoading && !cultivator) { - return ( -
-

地火引动中……

-
- ); - } - - return ( - -

{note}

- - ) : undefined - } - aside={ - <> - -
-

已投入灵材:{selectedMaterialIds.length} / {MAX_MATERIALS}

-

目标槽位:{requestedSlot ? '已选定' : '尚未指定'}

-

预计灵石:{displayEstimatedCost?.spiritStones ?? 0}

-
-
- -

槽位决定器型,灵材决定骨相,意念决定成品气质。

-

若资源不足或规则冲突,先在主区修正后再起炉。

- - ), - }} - /> - - } - > - - - - - -
-

- 已投入 {selectedMaterialIds.length} / {MAX_MATERIALS} 种灵材 -

- setIsMaterialModalOpen(true)} - disabled={isSubmitting} - > - 打开材料 - -
- toggleMaterial(id)} - onDoseChange={handleDoseChange} - /> -
- - - {displayEstimatedCost ? ( -
- - {SPIRIT_STONES_LABEL}: - - {displayEstimatedCost.spiritStones} - {' '} - 枚 - - - {displayCanAfford ? '✓ 资源充足' : '✗ 灵石不足'} - -
- ) : ( - 请先选择材料以查看消耗 - )} - - {displayValidation?.blockingReason && ( - {displayValidation.blockingReason} - )} -
- - - - - 重置 - - - {isSubmitting ? '真火炼中……' : '开炉炼器'} - - - - - {status && !createdResult && ( -
- {status} -
- )} - - setIsMaterialModalOpen(false)} - title="甄选炼器灵材" - maxMaterials={MAX_MATERIALS} - cultivatorId={cultivator?.id} - selectedMaterialIds={selectedMaterialIds} - onToggleMaterial={toggleMaterial} - selectedMaterialMap={selectedMaterialMap} - isSubmitting={isSubmitting} - pageSize={20} - includeMaterialTypes={ALLOWED_MATERIAL_TYPES} - refreshKey={materialsRefreshKey} - loadingText="正在检索储物袋中的灵材,请稍候……" - emptyNoticeText="暂无可用于炼器的材料。" - totalText={(total) => `共 ${total} 份可用于炼器的材料`} - /> - - setIsResultModalOpen(false)} - product={createdResult} - /> - - {celebrationTick > 0 && ( - - )} -
- ); + return ; } diff --git a/src/react-app/routes/game/retreat/route.tsx b/src/react-app/routes/game/retreat/route.tsx index f57d82f6..8bca834c 100644 --- a/src/react-app/routes/game/retreat/route.tsx +++ b/src/react-app/routes/game/retreat/route.tsx @@ -1,19 +1,9 @@ +import { RetreatView } from '@app/components/feature/retreat/RetreatView'; import { Suspense } from 'react'; -import { RetreatView } from './components/RetreatView'; -/** - * 洞府页面 - * 重构后仅保留路由壳子 - */ export default function RetreatPage() { return ( - -
洞府封闭中……
- - } - > +
洞府封闭中……
}>
); diff --git a/src/react-app/routes/game/sect/abilities/redirect.tsx b/src/react-app/routes/game/sect/abilities/redirect.tsx new file mode 100644 index 00000000..9fb7f4a1 --- /dev/null +++ b/src/react-app/routes/game/sect/abilities/redirect.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function SectAbilitiesRedirect() { + return ; +} diff --git a/src/react-app/routes/game/sect/abilities/route.tsx b/src/react-app/routes/game/sect/abilities/route.tsx index 486cf055..437cd38c 100644 --- a/src/react-app/routes/game/sect/abilities/route.tsx +++ b/src/react-app/routes/game/sect/abilities/route.tsx @@ -1,9 +1,4 @@ import { SectAbilityDetails } from '@app/components/feature/sect/SectAbilityDetails'; -import { - GameSceneFrame, - GameSceneLoading, - GameSceneNote, -} from '@app/components/game-shell'; import { InkModal } from '@app/components/layout'; import { useInkUI } from '@app/components/providers/InkUIProvider'; import { InkButton, InkCard, InkNotice } from '@app/components/ui'; @@ -18,6 +13,7 @@ import { } from '@shared/engine/sect'; import { resolveSectAbility } from '@shared/engine/sect/content'; import { useEffect, useMemo, useState } from 'react'; +import { SectPageLoading, SectScene } from '../components/SectScene'; const EMPTY_SLOTS: SectAbilitySlots = [null, null, null, null]; const SLOT_NAMES = ['', '一', '二', '三'] as const; @@ -155,18 +151,17 @@ export default function SectAbilitiesPage() { }; if (!data && !error) - return ; + return ; return ( - {error} : undefined + ? `演武场中央阵纹已启,${definition.name}神通将在当前流派与参悟方案下显化威能。` + : '阵台尚静。拜入宗门后,方可在此配置宗门神通。' } + error={error} + mood="arena" aside={ definition ? (
@@ -181,6 +176,7 @@ export default function SectAbilitiesPage() { ) : undefined } > +
{!sect || !definition ? ( 尚未拜入宗门。 ) : ( @@ -353,6 +349,7 @@ export default function SectAbilitiesPage() { )} - +
+ ); } diff --git a/src/react-app/routes/game/sect/affairs/route.tsx b/src/react-app/routes/game/sect/affairs/route.tsx new file mode 100644 index 00000000..3ada6af4 --- /dev/null +++ b/src/react-app/routes/game/sect/affairs/route.tsx @@ -0,0 +1,181 @@ +import { InkButton, InkCard, InkNotice, InkSelect } from '@app/components/ui'; +import { + useInventorySnapshot, + useProductsSnapshot, +} from '@app/lib/player-state/selectors'; +import { fetchSectTasks } from '@app/lib/sect/sectClient'; +import type { SectTaskId, SectTaskRecordData, SectTasksData } from '@shared/contracts/sect'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { + postJson, + SectPageLoading, + SectScene, + useSectMutation, +} from '../components/SectScene'; + +const taskNames: Record = { + gate_sweep: '清扫山门', + mine_patrol: '巡视矿场', + pill_delivery: '丹药委托', + artifact_delivery: '法宝委托', + weekly_diligence: '勤务周录', + weekly_tournament: '宗门小比', + weekly_bounty: '悬赏令', + elder_trial: '长老试炼', +}; + +export default function SectAffairsPage() { + const [tasks, setTasks] = useState(); + const [selectedMoves, setSelectedMoves] = useState([]); + const [selectedItemId, setSelectedItemId] = useState(''); + const inventory = useInventorySnapshot(); + const products = useProductsSnapshot(); + const reload = useCallback(async () => setTasks(await fetchSectTasks()), []); + useEffect(() => { + void fetchSectTasks().then(setTasks); + }, []); + const { busy, run } = useSectMutation(reload); + + const active = tasks?.dailyTask; + const submitItems = useMemo(() => { + if (!active) return []; + if (active.taskId === 'pill_delivery') + return inventory.consumables + .filter((item): item is typeof item & { id: string } => Boolean(item.id)) + .map((item) => ({ id: item.id, label: `${item.name} · ${item.quality} · ${item.quantity}枚` })); + if (active.taskId === 'artifact_delivery') + return products.artifacts + .filter((item): item is typeof item & { id: string } => Boolean(item.id) && !item.isEquipped) + .map((item) => ({ id: item.id, label: `${item.name} · ${item.quality}` })); + return []; + }, [active, inventory.consumables, products.artifacts]); + + if (!tasks) return ; + const bounty = tasks.weeklyTasks.find((task) => task.taskId === 'weekly_bounty'); + return ( +

今日:{tasks.dateKey}

本周:{tasks.weekKey}

悬赏轮换:{bounty?.payload?.mode === 'material' ? '稀有材料交付' : '叛徒残影战'}

} + > +
+

今日委托

+ {!active ? ( +
+ {tasks.dailyOffers.map((offer) => ( + +
{offer.name}+{offer.contributionReward} 贡献
+

{offer.description}

+ void run('/api/sects/current/tasks/daily/accept', postJson({ taskId: offer.id }), `已领取「${offer.name}」`)} + > + {offer.available ? '领取委托' : offer.unavailableReason ?? '尚未解锁'} + +
+ ))} +
+ ) : ( + + )} +
+ +
+

本周宗门录

+
+ {tasks.weeklyTasks.map((task) => { + const mode = task.payload?.mode; + return ( + +
{taskNames[task.taskId]}+{task.taskId === 'weekly_diligence' ? 20 : task.taskId === 'weekly_tournament' ? 40 : 60}
+

进度 {task.progress} / {task.target}

+ {task.taskId === 'weekly_bounty' ?

{mode === 'material' ? '本周需交付 2 份玄品以上材料' : '本周挑战叛徒残影'}

: null} + {task.status !== 'completed' && task.taskId === 'weekly_tournament' ? void run('/api/sects/current/tasks/weekly_tournament/challenge', postJson(), '宗门小比结算完成')}>参加小比 : null} + {task.status !== 'completed' && task.taskId === 'weekly_bounty' && mode !== 'material' ? void run('/api/sects/current/tasks/weekly_bounty/challenge', postJson(), '悬赏残影战结算完成')}>追缉残影 : null} + {task.status !== 'completed' && task.taskId === 'weekly_bounty' && mode === 'material' ? : null} + {task.status === 'completed' ?

本周已完成

: null} +
+ ); + })} +
+
+ +
+

晋升试炼

+ + 真传晋升需在满足金丹境、当前贡献 3000 并完成悬赏后,通过传功长老剑影试炼。 + void run('/api/sects/current/tasks/elder_trial/challenge', postJson(), '长老试炼结算完成')}> + {tasks.promotionTask?.status === 'completed' ? '试炼已通过' : '挑战长老试炼'} + + +
+ + ); +} + +function DailyTaskCard({ + task, + busy, + selectedMoves, + setSelectedMoves, + selectedItemId, + setSelectedItemId, + items, + run, +}: { + task: SectTaskRecordData; + busy: boolean; + selectedMoves: number[]; + setSelectedMoves: (moves: number[]) => void; + selectedItemId: string; + setSelectedItemId: (id: string) => void; + items: Array<{ id: string; label: string }>; + run: (url: string, init: RequestInit, message: string) => Promise; +}) { + if (task.status === 'completed') return 今日「{taskNames[task.taskId]}」已完成,明日可重新择取委托。; + if (task.taskId === 'gate_sweep') { + return ( + + 清扫山门 · 云阶落叶 +

点选落叶所在石阶。服务端会核验本日种子生成的目标。

+
+ {Array.from({ length: 9 }, (_, index) => ( + + ))} +
+ void run('/api/sects/current/tasks/gate_sweep/complete', postJson({ moves: selectedMoves }), '山门清扫完成')}>提交勤务 +
+ ); + } + if (task.taskId === 'mine_patrol') return 巡视矿场

矿脉附近出现侵扰妖兽,击退后自动结算今日奖励。

void run('/api/sects/current/tasks/mine_patrol/challenge', postJson(), '矿场巡视结算完成')}>前往迎战
; + return ( + + {taskNames[task.taskId]} + {task.taskId === 'pill_delivery' ?

本次要求:{task.payload?.pillFamily === 'healing' ? '疗伤类' : '回气类'}丹药,凡品以上,1 枚。

: null} + + + {items.map((item) => )} + + void run(`/api/sects/current/tasks/${task.taskId}/submit`, postJson({ itemId: selectedItemId, quantity: 1 }), '委托交付完成')}>确认交付 +
+ ); +} + +function MaterialBounty({ task, busy, run }: { task: SectTaskRecordData; busy: boolean; run: (url: string, init: RequestInit, message: string) => Promise }) { + const inventory = useInventorySnapshot(); + const [itemId, setItemId] = useState(''); + return
{inventory.materials.map((item) => )} void run(`/api/sects/current/tasks/${task.taskId}/submit`, postJson({ itemId, quantity: Number(task.payload?.quantity ?? 2) }), '悬赏材料已交付')}>交付材料
; +} diff --git a/src/react-app/routes/game/sect/alchemy/route.tsx b/src/react-app/routes/game/sect/alchemy/route.tsx new file mode 100644 index 00000000..971294aa --- /dev/null +++ b/src/react-app/routes/game/sect/alchemy/route.tsx @@ -0,0 +1,12 @@ +import { AlchemyScene } from '@app/components/feature/craft/AlchemyScene'; +import { SectPageLoading, useSectCurrentData } from '../components/SectScene'; + +export default function SectAlchemyPage() { + const { data } = useSectCurrentData(); + if (!data) return ; + + const level = data.overview?.facilities.find((item) => item.key === 'workshop')?.level ?? 1; + const rankDiscount = data.sect?.discipleRank === 'true' ? 10 : 0; + + return ; +} diff --git a/src/react-app/routes/game/sect/archive/abilities/route.tsx b/src/react-app/routes/game/sect/archive/abilities/route.tsx new file mode 100644 index 00000000..07aa58d1 --- /dev/null +++ b/src/react-app/routes/game/sect/archive/abilities/route.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function SectArchiveAbilitiesRedirect() { + return ; +} diff --git a/src/react-app/routes/game/sect/archive/methods/route.tsx b/src/react-app/routes/game/sect/archive/methods/route.tsx new file mode 100644 index 00000000..ea5828a2 --- /dev/null +++ b/src/react-app/routes/game/sect/archive/methods/route.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function SectMethodsRedirect() { + return ; +} diff --git a/src/react-app/routes/game/sect/archive/paths/route.tsx b/src/react-app/routes/game/sect/archive/paths/route.tsx new file mode 100644 index 00000000..e6b9d91b --- /dev/null +++ b/src/react-app/routes/game/sect/archive/paths/route.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function SectPathsRedirect() { + return ; +} diff --git a/src/react-app/routes/game/sect/archive/route.tsx b/src/react-app/routes/game/sect/archive/route.tsx new file mode 100644 index 00000000..5b325f7e --- /dev/null +++ b/src/react-app/routes/game/sect/archive/route.tsx @@ -0,0 +1,42 @@ +import { useActiveCultivatorProfile } from '@app/lib/player-state/selectors'; +import { MethodsTab } from '../components/MethodsTab'; +import { + SectPageLoading, + SectScene, + useSectCurrentData, + useSectMutation, +} from '../components/SectScene'; + +export default function SectArchivePage() { + const { data, error, reload } = useSectCurrentData(); + const cultivator = useActiveCultivatorProfile(); + const { busy, run } = useSectMutation(reload); + + if (!data) return ; + + return ( + +

当前心法上限:{data.methodLevelCap}级

+

境界上限:{data.overview?.realmMethodLevelCap ?? data.methodLevelCap}级

+ + } + > +
+ { + await run(url, init, '心法研习完成'); + }} + realm={cultivator?.realm ?? '炼气'} + /> +
+
+ ); +} diff --git a/src/react-app/routes/game/sect/arena/route.tsx b/src/react-app/routes/game/sect/arena/route.tsx new file mode 100644 index 00000000..9f9d70b5 --- /dev/null +++ b/src/react-app/routes/game/sect/arena/route.tsx @@ -0,0 +1 @@ +export { default } from '../abilities/route'; diff --git a/src/react-app/routes/game/sect/cave/route.tsx b/src/react-app/routes/game/sect/cave/route.tsx new file mode 100644 index 00000000..fa71d3da --- /dev/null +++ b/src/react-app/routes/game/sect/cave/route.tsx @@ -0,0 +1,10 @@ +import { InkCard, InkNotice } from '@app/components/ui'; +import { hasSectRank } from '@shared/engine/sect'; +import { SectPageLoading, SectScene, useSectCurrentData } from '../components/SectScene'; + +export default function SectCavePage() { + const { data, error } = useSectCurrentData(); + if (!data) return ; + const unlocked = hasSectRank(data.sect?.discipleRank ?? 'registered', 'inner'); + return {unlocked ?

云竹洞府 · 石门已启

香炉、蒲团与一方石案已经备好。此处只记录你的内门洞府资格,暂不提供额外数值收益。

: 私人洞府须晋升内门弟子后开放。}
; +} diff --git a/src/react-app/routes/game/sect/components/CommissionsTab.tsx b/src/react-app/routes/game/sect/components/CommissionsTab.tsx deleted file mode 100644 index 49d62ce6..00000000 --- a/src/react-app/routes/game/sect/components/CommissionsTab.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { InkButton, InkNotice } from '@app/components/ui'; -import type { SectCurrentData } from '@shared/contracts/sect'; -import { sectJsonRequest, type SectAction } from './types'; - -export function CommissionsTab({ - data, - busy, - action, -}: { - data: SectCurrentData; - busy: boolean; - action: SectAction; -}) { - if (!data.sect) return 拜师后方可承接宗门委托。; - return ( -
- - {data.commission.claimedAt - ? '今日委托奖励已领取。' - : data.commission.completedAt - ? '今日委托已完成,可领取奖励。' - : '今日尚未完成宗门委托。'} - -
- {!data.commission.completedAt ? ( - - void action( - '/api/sects/current/commissions/spar', - sectJsonRequest('POST'), - ) - } - > - 完成切磋委托 - - ) : null} - {data.commission.completedAt && !data.commission.claimedAt ? ( - - void action( - '/api/sects/current/commissions/claim', - sectJsonRequest('POST'), - ) - } - > - 领取贡献 - - ) : null} -
-
- ); -} diff --git a/src/react-app/routes/game/sect/components/SectScene.tsx b/src/react-app/routes/game/sect/components/SectScene.tsx new file mode 100644 index 00000000..9a2499dc --- /dev/null +++ b/src/react-app/routes/game/sect/components/SectScene.tsx @@ -0,0 +1,147 @@ +/* eslint-disable react-refresh/only-export-components -- scene components and their tightly coupled hooks share one private module */ +import { + GameSceneFrame, + GameSceneLoading, + GameSceneNote, +} from '@app/components/game-shell'; +import { useInkUI } from '@app/components/providers/InkUIProvider'; +import { InkButton } from '@app/components/ui'; +import { usePlayerStateActions } from '@app/lib/player-state/store'; +import { fetchSectCurrent } from '@app/lib/sect/sectClient'; +import type { SectCurrentData } from '@shared/contracts/sect'; +import { SECT_RANK_LABELS, type SectDiscipleRank } from '@shared/engine/sect'; +import type { ReactNode } from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { useNavigate } from 'react-router'; + +export type SectSceneMood = + | 'hall' + | 'affairs' + | 'archive' + | 'cliff' + | 'arena' + | 'treasury' + | 'industries' + | 'cultivation' + | 'alchemy' + | 'refinery' + | 'vein' + | 'garden' + | 'cave' + | 'gate'; + +const moodStyles: Record = { + hall: { surface: 'border-amber-900/20 bg-[radial-gradient(circle_at_50%_0%,rgba(146,83,37,0.18),transparent_44%),linear-gradient(180deg,rgba(250,244,224,0.92),rgba(242,229,199,0.5))]', emblem: '殿' }, + affairs: { surface: 'border-stone-700/20 bg-[linear-gradient(110deg,rgba(101,68,43,0.12),transparent_35%),repeating-linear-gradient(90deg,rgba(255,255,255,0.3)_0_1px,transparent_1px_72px)]', emblem: '令' }, + archive: { surface: 'border-amber-800/20 bg-[radial-gradient(circle_at_18%_18%,rgba(173,126,50,0.18),transparent_32%),linear-gradient(135deg,rgba(248,240,215,0.95),rgba(229,211,169,0.55))]', emblem: '经' }, + cliff: { surface: 'border-sky-900/15 bg-[radial-gradient(circle_at_72%_8%,rgba(255,255,255,0.9),transparent_28%),linear-gradient(155deg,rgba(218,233,226,0.85),rgba(237,230,207,0.65))]', emblem: '悟' }, + arena: { surface: 'border-red-900/20 bg-[radial-gradient(circle_at_50%_45%,transparent_0_19%,rgba(113,31,31,0.08)_20%_21%,transparent_22%_34%,rgba(113,31,31,0.07)_35%_36%,transparent_37%),linear-gradient(180deg,rgba(231,221,204,0.9),rgba(210,197,177,0.62))]', emblem: '武' }, + treasury: { surface: 'border-yellow-900/20 bg-[linear-gradient(135deg,rgba(147,104,30,0.16),transparent_42%),repeating-linear-gradient(0deg,rgba(93,63,25,0.06)_0_2px,transparent_2px_62px)]', emblem: '藏' }, + industries: { surface: 'border-slate-800/20 bg-[linear-gradient(120deg,rgba(73,84,83,0.1),transparent_45%),repeating-linear-gradient(45deg,rgba(60,70,66,0.045)_0_1px,transparent_1px_18px)]', emblem: '造' }, + cultivation: { surface: 'border-teal-900/20 bg-[radial-gradient(circle_at_50%_44%,rgba(121,190,177,0.2)_0_9%,transparent_10%_20%,rgba(66,117,111,0.09)_21%_22%,transparent_23%),linear-gradient(180deg,rgba(225,237,224,0.9),rgba(226,216,192,0.55))]', emblem: '静' }, + alchemy: { surface: 'border-orange-950/20 bg-[radial-gradient(circle_at_50%_85%,rgba(197,71,20,0.22),transparent_28%),linear-gradient(145deg,rgba(244,226,190,0.92),rgba(202,171,124,0.48))]', emblem: '丹' }, + refinery: { surface: 'border-slate-950/25 bg-[linear-gradient(135deg,rgba(56,63,66,0.18),transparent_45%),radial-gradient(circle_at_78%_74%,rgba(182,73,30,0.16),transparent_24%)]', emblem: '器' }, + vein: { surface: 'border-cyan-950/20 bg-[linear-gradient(125deg,transparent_28%,rgba(79,176,184,0.16)_29%_31%,transparent_32%_54%,rgba(61,144,153,0.12)_55%_57%,transparent_58%),linear-gradient(180deg,rgba(213,226,218,0.86),rgba(191,199,185,0.58))]', emblem: '脉' }, + garden: { surface: 'border-emerald-950/20 bg-[repeating-linear-gradient(165deg,rgba(53,111,70,0.08)_0_2px,transparent_2px_34px),linear-gradient(135deg,rgba(218,232,201,0.9),rgba(235,220,179,0.55))]', emblem: '药' }, + cave: { surface: 'border-stone-800/20 bg-[radial-gradient(ellipse_at_30%_25%,rgba(255,255,255,0.65),transparent_28%),linear-gradient(140deg,rgba(212,214,202,0.9),rgba(181,176,161,0.55))]', emblem: '隐' }, + gate: { surface: 'border-sky-950/15 bg-[linear-gradient(180deg,rgba(214,229,226,0.88),rgba(239,227,198,0.6)),repeating-linear-gradient(90deg,transparent_0_80px,rgba(48,72,70,0.05)_80px_81px)]', emblem: '山' }, +}; + +export function useSectCurrentData() { + const [data, setData] = useState(); + const [error, setError] = useState(); + const reload = useCallback(async () => { + try { + setData(await fetchSectCurrent()); + setError(undefined); + } catch (reason) { + setError(reason instanceof Error ? reason.message : '宗门卷宗读取失败'); + } + }, []); + useEffect(() => { + void fetchSectCurrent().then(setData).catch((reason) => { + setError(reason instanceof Error ? reason.message : '宗门卷宗读取失败'); + }); + }, []); + return { data, error, reload, setData }; +} + +export function useSectMutation(onDone?: () => Promise | void) { + const [busy, setBusy] = useState(false); + const { mutate } = usePlayerStateActions(); + const { pushToast } = useInkUI(); + const run = useCallback( + async (url: string, init: RequestInit, successMessage: string) => { + setBusy(true); + try { + const result = await mutate(fetch(url, init)); + await onDone?.(); + pushToast({ message: successMessage, tone: 'success' }); + return result; + } catch (reason) { + pushToast({ + message: reason instanceof Error ? reason.message : '宗门事务失败', + tone: 'danger', + }); + return undefined; + } finally { + setBusy(false); + } + }, + [mutate, onDone, pushToast], + ); + return { busy, run }; +} + +export function SectScene({ + title, + description, + children, + aside, + error, + mood = 'hall', +}: { + title: string; + description: string; + children: ReactNode; + aside?: ReactNode; + error?: string; + mood?: SectSceneMood; +}) { + const navigate = useNavigate(); + const atmosphere = moodStyles[mood]; + return ( + {error} : undefined + } + aside={aside} + contentClassName="lg:max-w-none" + > +
+ +
+ navigate('/game/sect')} variant="secondary">返回宗门总视图 + +
+
{children}
+
+
+ ); +} + +export function SectPageLoading({ message = '云雾间卷宗渐次显现……' }: { message?: string }) { + return ; +} + +export function rankLabel(rank?: SectDiscipleRank) { + return rank ? SECT_RANK_LABELS[rank] : '未入门'; +} + +export const postJson = (body?: unknown): RequestInit => ({ + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + ...(body === undefined ? {} : { body: JSON.stringify(body) }), +}); diff --git a/src/react-app/routes/game/sect/cultivation-room/route.tsx b/src/react-app/routes/game/sect/cultivation-room/route.tsx new file mode 100644 index 00000000..5063e87b --- /dev/null +++ b/src/react-app/routes/game/sect/cultivation-room/route.tsx @@ -0,0 +1,16 @@ +import { RetreatView } from '@app/components/feature/retreat/RetreatView'; +import { Suspense } from 'react'; +import { SectPageLoading, useSectCurrentData } from '../components/SectScene'; + +export default function SectCultivationRoomPage() { + const { data } = useSectCurrentData(); + if (!data) return ; + + const level = data.overview?.facilities.find((item) => item.key === 'cultivation_room')?.level ?? 1; + + return ( + }> + + + ); +} diff --git a/src/react-app/routes/game/sect/enlightenment-cliff/route.tsx b/src/react-app/routes/game/sect/enlightenment-cliff/route.tsx new file mode 100644 index 00000000..d3b98d8c --- /dev/null +++ b/src/react-app/routes/game/sect/enlightenment-cliff/route.tsx @@ -0,0 +1,38 @@ +import { useActiveCultivatorProfile } from '@app/lib/player-state/selectors'; +import { PathsTab } from '../components/PathsTab'; +import { + SectPageLoading, + SectScene, + useSectCurrentData, + useSectMutation, +} from '../components/SectScene'; + +export default function SectEnlightenmentCliffPage() { + const { data, error, reload } = useSectCurrentData(); + const cultivator = useActiveCultivatorProfile(); + const { busy, run } = useSectMutation(reload); + + if (!data) return ; + + return ( + +
+
+
+ ); +} diff --git a/src/react-app/routes/game/sect/gate/route.tsx b/src/react-app/routes/game/sect/gate/route.tsx new file mode 100644 index 00000000..fa76fd37 --- /dev/null +++ b/src/react-app/routes/game/sect/gate/route.tsx @@ -0,0 +1,9 @@ +import { InkCard, InkNotice } from '@app/components/ui'; +import { SectPageLoading, SectScene, useSectCurrentData } from '../components/SectScene'; + +export default function SectGatePage() { + const { data, error } = useSectCurrentData(); + if (!data) return ; + const project = data.overview?.project; + return
; +} diff --git a/src/react-app/routes/game/sect/hall/route.tsx b/src/react-app/routes/game/sect/hall/route.tsx new file mode 100644 index 00000000..b7efe661 --- /dev/null +++ b/src/react-app/routes/game/sect/hall/route.tsx @@ -0,0 +1,118 @@ +import { InkButton, InkCard, InkNotice } from '@app/components/ui'; +import { + fetchSectMembers, + fetchSectOverview, +} from '@app/lib/sect/sectClient'; +import type { SectMembersData, SectOverviewData } from '@shared/contracts/sect'; +import { + SECT_FACILITY_LABELS, + SECT_RANK_LABELS, +} from '@shared/engine/sect'; +import { useCallback, useEffect, useState } from 'react'; +import { + postJson, + rankLabel, + SectPageLoading, + SectScene, + useSectCurrentData, + useSectMutation, +} from '../components/SectScene'; + +export default function SectHallPage() { + const { data, error, reload: reloadCurrent } = useSectCurrentData(); + const [overview, setOverview] = useState(); + const [members, setMembers] = useState(); + const reload = useCallback(async () => { + await reloadCurrent(); + const [nextOverview, nextMembers] = await Promise.all([ + fetchSectOverview(), + fetchSectMembers(), + ]); + setOverview(nextOverview); + setMembers(nextMembers); + }, [reloadCurrent]); + useEffect(() => { + void Promise.all([fetchSectOverview(), fetchSectMembers()]).then( + ([nextOverview, nextMembers]) => { + setOverview(nextOverview); + setMembers(nextMembers); + }, + ); + }, []); + const { busy, run } = useSectMutation(reload); + + if (!data || !overview || !members) return ; + const sect = data.sect; + if (!sect) return 尚未拜入宗门。; + const rank = sect.discipleRank ?? 'registered'; + return ( +

当前身份:{rankLabel(rank)}

宗门贡献:{sect.contribution}

心法上限:{overview.methodLevelCap}级

} + > +
+ +

弟子身份

+ {SECT_RANK_LABELS[rank]} +

贡献余额 {sect.contribution}

+ {overview.nextRank ? ( + <> +

下一职阶:{SECT_RANK_LABELS[overview.nextRank]}

+ {overview.promotionMissing.length ? ( +
    + {overview.promotionMissing.map((item) =>
  • {item}
  • )} +
+ ) :

晋升条件均已满足

} + 0} + onClick={() => void run('/api/sects/current/promotion', postJson(), '弟子职阶晋升完成')} + > + 晋升{SECT_RANK_LABELS[overview.nextRank]} + + + ) :

已列真传

} +
+ + +

本周俸禄

+ {overview.stipend.spiritStones.toLocaleString()} 灵石 +

药田灵草 {overview.stipend.herbQuantity} 份

+ {overview.stipend.bonusRewards.map((reward) =>

{reward}

)} +

周次 {overview.stipend.weekKey}

+ void run('/api/sects/current/stipend/claim', postJson(), '本周宗门俸禄已入账')} + > + {overview.stipend.claimed ? '本周已领取' : '领取周俸'} + +
+ + +

长老工程

+ {overview.project ? ( + <> + {SECT_FACILITY_LABELS[overview.project.facilityKey]} · {overview.project.targetLevel}级 +

{overview.project.progress.toLocaleString()} / {overview.project.target.toLocaleString()} 建设点

+
+ + ) :

本周工程尚在议定,或已经完成。

} + +
+ +
+

同门名录

共 {members.total} 位正式成员,职务首版仅作展示。

+
+ + + {members.items.map((member) => )} +
名号境界身份职务
{member.name}{member.realm}{member.realmStage}{SECT_RANK_LABELS[member.discipleRank]}{member.office === 'none' ? '无' : member.office}
+
+
+ + ); +} diff --git a/src/react-app/routes/game/sect/herb-garden/route.tsx b/src/react-app/routes/game/sect/herb-garden/route.tsx new file mode 100644 index 00000000..e7ac9bc0 --- /dev/null +++ b/src/react-app/routes/game/sect/herb-garden/route.tsx @@ -0,0 +1,41 @@ +import { SectPageLoading, SectScene, useSectCurrentData } from '../components/SectScene'; + +const herbStages = ['新畦初醒', '灵苗成行', '药香盈陌', '四时不歇', '百草丰登'] as const; + +export default function SectHerbGardenPage() { + const { data, error } = useSectCurrentData(); + if (!data) return ; + + const facility = data.overview?.facilities.find((item) => item.key === 'herb_garden'); + const level = facility?.level ?? 1; + + return ( +

药田等级:{level}级

每周基础灵草:{level}份

} + > +
+
+

今日长势

+ {herbStages[level - 1] ?? herbStages[4]} +

药童已按节气完成灌溉与除虫。这里的产出按周结算,无需额外采收,也不会占用建设捐献额度。

+
+
+ {Array.from({ length: 5 }, (_, index) => { + const active = index < level; + return ( +
+
+ ); + })} +
+
+
+ ); +} diff --git a/src/react-app/routes/game/sect/industries/route.tsx b/src/react-app/routes/game/sect/industries/route.tsx new file mode 100644 index 00000000..1177e9b4 --- /dev/null +++ b/src/react-app/routes/game/sect/industries/route.tsx @@ -0,0 +1,63 @@ +import { InkButton, InkCard, InkSelect } from '@app/components/ui'; +import { + useInventorySnapshot, + useProductsSnapshot, +} from '@app/lib/player-state/selectors'; +import { fetchSectConstruction } from '@app/lib/sect/sectClient'; +import type { SectConstructionData, SectDonationDemandData } from '@shared/contracts/sect'; +import { SECT_FACILITY_LABELS } from '@shared/engine/sect'; +import { useCallback, useEffect, useState } from 'react'; +import { postJson, SectPageLoading, SectScene, useSectMutation } from '../components/SectScene'; + +export default function SectIndustriesPage() { + const [data, setData] = useState(); + const reload = useCallback(async () => setData(await fetchSectConstruction()), []); + useEffect(() => { + void fetchSectConstruction().then(setData); + }, []); + const { busy, run } = useSectMutation(reload); + if (!data) return ; + return ( +

今日建设贡献:{data.donatedContributionToday} / {data.dailyContributionCap}

剩余额度:{Math.max(0, data.dailyContributionCap - data.donatedContributionToday)}

}> +
+

本周工程

+ + {data.project ? <>
{SECT_FACILITY_LABELS[data.project.facilityKey]}升至 {data.project.targetLevel} 级{Math.floor((data.project.progress / data.project.target) * 100)}%

{data.project.progress.toLocaleString()} / {data.project.target.toLocaleString()} 建设点

: <>长老工程待启

提交首笔有效建设捐献时,将按本周优先级原子开启工程。

} + +
+ +
+

设施总览

+
+ {data.facilities.map((facility) =>
{SECT_FACILITY_LABELS[facility.key]}{facility.key === 'formation' ? '锁定' : `${facility.level}级`}
)} +
+
+ +
+

今日长老需求

+
+ {data.demands.map((demand) => )} +
+
+
+

近期建设动态

+ {data.recentActivity.length ?
    {data.recentActivity.map((item) =>
  • {item.memberName} 完成一笔「{item.demandId}」捐献,宗门建设 +{item.constructionPoints},个人贡献 +{item.contribution}
  • )}
:

尚无建设记录,第一笔捐献将记入此册。

} +
+
+ ); +} + +function DonationCard({ demand, busy, capRemaining, run }: { demand: SectDonationDemandData; busy: boolean; capRemaining: number; run: (url: string, init: RequestInit, message: string) => Promise }) { + const inventory = useInventorySnapshot(); + const products = useProductsSnapshot(); + const [itemId, setItemId] = useState(''); + const options = demand.kind === 'material' + ? inventory.materials.filter((item): item is typeof item & { id: string } => Boolean(item.id)).map((item) => ({ id: item.id, label: `${item.name} · ${item.rank} · ${item.quantity}份` })) + : demand.kind === 'pill' + ? inventory.consumables.filter((item): item is typeof item & { id: string } => Boolean(item.id)).map((item) => ({ id: item.id, label: `${item.name} · ${item.quality} · ${item.quantity}枚` })) + : demand.kind === 'artifact' + ? products.artifacts.filter((item): item is typeof item & { id: string } => Boolean(item.id) && !item.isEquipped).map((item) => ({ id: item.id, label: `${item.name} · ${item.quality}` })) + : []; + const needsItem = demand.kind !== 'spirit_stones'; + return
{demand.name}+{demand.contribution} 贡献

{demand.description}

同时增加 {demand.constructionPoints} 建设点

{needsItem ? {options.map((item) => )} : null} void run('/api/sects/current/construction/donate', postJson({ demandId: demand.id, itemId: itemId || undefined, quantity: 1, requestId: crypto.randomUUID() }), '建设捐献已入账')}>{capRemaining < demand.contribution ? '今日额度不足' : '捐献一份'}
; +} diff --git a/src/react-app/routes/game/sect/mapConfig.test.ts b/src/react-app/routes/game/sect/mapConfig.test.ts new file mode 100644 index 00000000..05efc6e5 --- /dev/null +++ b/src/react-app/routes/game/sect/mapConfig.test.ts @@ -0,0 +1,68 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; +import { SECT_MAP_HOTSPOTS } from './mapConfig'; + +describe('sect map configuration', () => { + it('keeps every map hotspot and route unique', () => { + const ids = SECT_MAP_HOTSPOTS.map((spot) => spot.id); + const routes = SECT_MAP_HOTSPOTS.flatMap((spot) => spot.route ? [spot.route] : []); + + expect(new Set(ids).size).toBe(ids.length); + expect(new Set(routes).size).toBe(routes.length); + expect(routes).toEqual(expect.arrayContaining([ + '/game/sect/archive', + '/game/sect/enlightenment-cliff', + '/game/sect/arena', + '/game/sect/alchemy', + '/game/sect/refinery', + '/game/sect/spirit-vein', + '/game/sect/herb-garden', + ])); + }); + + it('keeps the formation locked and all other hotspots navigable', () => { + const locked = SECT_MAP_HOTSPOTS.filter((spot) => spot.locked); + expect(locked).toHaveLength(1); + expect(locked[0]?.id).toBe('formation'); + expect(locked[0]?.route).toBeUndefined(); + expect(SECT_MAP_HOTSPOTS.filter((spot) => !spot.locked).every((spot) => Boolean(spot.route))).toBe(true); + }); + + it('uses the router and a unified transform canvas instead of native anchors', () => { + const source = readFileSync('src/react-app/routes/game/sect/route.tsx', 'utf8'); + expect(source).toContain('useNavigate'); + expect(source).toContain('TransformWrapper'); + expect(source).toContain('TransformComponent'); + expect(source).not.toContain(' { + const redirects = [ + ['src/react-app/routes/game/sect/archive/methods/route.tsx', '/game/sect/archive'], + ['src/react-app/routes/game/sect/archive/paths/route.tsx', '/game/sect/enlightenment-cliff'], + ['src/react-app/routes/game/sect/archive/abilities/route.tsx', '/game/sect/arena'], + ['src/react-app/routes/game/sect/abilities/redirect.tsx', '/game/sect/arena'], + ['src/react-app/routes/game/sect/workshop/route.tsx', '/game/sect'], + ] as const; + + for (const [file, target] of redirects) { + const source = readFileSync(file, 'utf8'); + expect(source).toContain(`to="${target}"`); + expect(source).toContain('replace'); + } + }); + + it('reuses shared workbench scenes for normal and sect routes', () => { + const routePairs = [ + ['src/react-app/routes/game/craft/alchemy/route.tsx', 'src/react-app/routes/game/sect/alchemy/route.tsx', 'AlchemyScene'], + ['src/react-app/routes/game/craft/refine/route.tsx', 'src/react-app/routes/game/sect/refinery/route.tsx', 'RefineScene'], + ['src/react-app/routes/game/retreat/route.tsx', 'src/react-app/routes/game/sect/cultivation-room/route.tsx', 'RetreatView'], + ] as const; + + for (const [normalRoute, sectRoute, component] of routePairs) { + expect(readFileSync(normalRoute, 'utf8')).toContain(component); + expect(readFileSync(sectRoute, 'utf8')).toContain(component); + } + }); +}); diff --git a/src/react-app/routes/game/sect/mapConfig.ts b/src/react-app/routes/game/sect/mapConfig.ts new file mode 100644 index 00000000..4e300c2d --- /dev/null +++ b/src/react-app/routes/game/sect/mapConfig.ts @@ -0,0 +1,30 @@ +import type { SectFacilityKey } from '@shared/engine/sect'; + +export type SectMapHotspot = { + id: string; + label: string; + route?: string; + facility?: SectFacilityKey; + left: string; + top: string; + note: string; + locked?: boolean; +}; + +export const SECT_MAP_HOTSPOTS: readonly SectMapHotspot[] = [ + { id: 'hall', label: '宗门大殿', route: '/game/sect/hall', left: '46%', top: '25%', note: '身份 · 晋升 · 周俸' }, + { id: 'archive', label: '藏经阁', route: '/game/sect/archive', facility: 'archive', left: '15%', top: '31%', note: '心法研习' }, + { id: 'cliff', label: '悟道崖', route: '/game/sect/enlightenment-cliff', left: '88%', top: '19%', note: '流派 · 经脉' }, + { id: 'arena', label: '演武台', route: '/game/sect/arena', left: '57%', top: '64%', note: '神通 · 战术' }, + { id: 'affairs', label: '执事堂', route: '/game/sect/affairs', left: '42%', top: '48%', note: '日常 · 周常 · 悬赏' }, + { id: 'treasury', label: '宗门宝库', route: '/game/sect/treasury', left: '57%', top: '36%', note: '贡献兑换' }, + { id: 'industries', label: '百业院', route: '/game/sect/industries', left: '55%', top: '53%', note: '宗门共建' }, + { id: 'cultivation', label: '修炼室', route: '/game/sect/cultivation-room', facility: 'cultivation_room', left: '17%', top: '72%', note: '聚灵闭关' }, + { id: 'alchemy', label: '丹房', route: '/game/sect/alchemy', facility: 'workshop', left: '64%', top: '43%', note: '丹火炼药' }, + { id: 'refinery', label: '器坊', route: '/game/sect/refinery', facility: 'workshop', left: '69%', top: '51%', note: '地火铸器' }, + { id: 'vein', label: '灵脉矿场', route: '/game/sect/spirit-vein', facility: 'spirit_vein', left: '84%', top: '46%', note: '灵石俸禄加成' }, + { id: 'garden', label: '药田', route: '/game/sect/herb-garden', facility: 'herb_garden', left: '83%', top: '75%', note: '每周灵草产出' }, + { id: 'gate', label: '山门', route: '/game/sect/gate', left: '48%', top: '77%', note: '宗门动态' }, + { id: 'cave', label: '私人洞府', route: '/game/sect/cave', left: '26%', top: '66%', note: '内门弟子居所' }, + { id: 'formation', label: '护山大阵', facility: 'formation', left: '49%', top: '8%', note: '宗门战后续开放', locked: true }, +] as const; diff --git a/src/react-app/routes/game/sect/refinery/route.tsx b/src/react-app/routes/game/sect/refinery/route.tsx new file mode 100644 index 00000000..be82dc21 --- /dev/null +++ b/src/react-app/routes/game/sect/refinery/route.tsx @@ -0,0 +1,12 @@ +import { RefineScene } from '@app/components/feature/craft/RefineScene'; +import { SectPageLoading, useSectCurrentData } from '../components/SectScene'; + +export default function SectRefineryPage() { + const { data } = useSectCurrentData(); + if (!data) return ; + + const level = data.overview?.facilities.find((item) => item.key === 'workshop')?.level ?? 1; + const rankDiscount = data.sect?.discipleRank === 'true' ? 10 : 0; + + return ; +} diff --git a/src/react-app/routes/game/sect/route.tsx b/src/react-app/routes/game/sect/route.tsx index 7e372af3..6a10a898 100644 --- a/src/react-app/routes/game/sect/route.tsx +++ b/src/react-app/routes/game/sect/route.tsx @@ -4,34 +4,29 @@ import { GameSceneNote, } from '@app/components/game-shell'; import { useInkUI } from '@app/components/providers/InkUIProvider'; -import { InkTabs } from '@app/components/ui'; -import { useActiveCultivatorProfile } from '@app/lib/player-state/selectors'; +import { InkButton } from '@app/components/ui'; import { usePlayerStateActions } from '@app/lib/player-state/store'; import { fetchSectCatalog, fetchSectCurrent } from '@app/lib/sect/sectClient'; import type { SectCatalogData, SectCurrentData } from '@shared/contracts/sect'; -import type { CultivatorSectState } from '@shared/engine/sect'; -import { useCallback, useEffect, useState } from 'react'; -import { CommissionsTab } from './components/CommissionsTab'; +import { + SECT_FACILITY_LABELS, + SECT_RANK_LABELS, + type CultivatorSectState, +} from '@shared/engine/sect'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; +import { useNavigate } from 'react-router'; import { GateTab } from './components/GateTab'; -import { MethodsTab } from './components/MethodsTab'; -import { PathsTab } from './components/PathsTab'; - -const tabs = [ - { value: 'gate', label: '山门' }, - { value: 'methods', label: '心法' }, - { value: 'paths', label: '流派' }, - { value: 'commissions', label: '委托' }, -]; +import { SECT_MAP_HOTSPOTS } from './mapConfig'; export default function SectPage() { const [catalog, setCatalog] = useState(); const [data, setData] = useState(); - const [tab, setTab] = useState('gate'); const [busy, setBusy] = useState(false); const [error, setError] = useState(); const { mutate } = usePlayerStateActions(); const { pushToast } = useInkUI(); - const cultivator = useActiveCultivatorProfile(); + const navigate = useNavigate(); const reload = useCallback(async () => { const [nextCatalog, nextData] = await Promise.all([ @@ -43,24 +38,15 @@ export default function SectPage() { }, []); useEffect(() => { - let cancelled = false; void Promise.all([fetchSectCatalog(), fetchSectCurrent()]) .then(([nextCatalog, nextData]) => { - if (!cancelled) { - setCatalog(nextCatalog); - setData(nextData); - } + setCatalog(nextCatalog); + setData(nextData); }) - .catch((reason) => { - if (!cancelled) - setError( - reason instanceof Error ? reason.message : '宗门卷宗读取失败', - ); - }); - return () => { - cancelled = true; - }; - }, [reload]); + .catch((reason) => + setError(reason instanceof Error ? reason.message : '宗门卷宗读取失败'), + ); + }, []); const action = useCallback( async (url: string, init: RequestInit) => { @@ -68,7 +54,7 @@ export default function SectPage() { try { await mutate<{ sect: CultivatorSectState }>(fetch(url, init)); await reload(); - pushToast({ message: '宗门事务已办妥', tone: 'success' }); + pushToast({ message: '拜师礼成,宗门舆图已开启', tone: 'success' }); } catch (reason) { pushToast({ message: reason instanceof Error ? reason.message : '宗门事务失败', @@ -81,57 +67,93 @@ export default function SectPage() { [mutate, pushToast, reload], ); + const facilities = useMemo( + () => new Map(data?.overview?.facilities.map((item) => [item.key, item])), + [data?.overview?.facilities], + ); + if (!catalog || !data) return ; - const definition = data.definition; - const sect = data.sect; + if (!data.sect || !data.definition) { + return ( + {error} : undefined} + > + + + ); + } + + const rank = data.sect.discipleRank ?? 'registered'; return ( {error} : undefined - } + title={`【${data.definition.name}舆图】`} + description="云海诸峰各司其职。择一处落下遁光,进入对应设施办理宗门事务。" aside={
-

- 种族:{catalog.playerRace === 'human' ? '人族' : catalog.playerRace} -

-

身份:{sect ? `${definition?.name ?? '宗门'}弟子` : '山外散修'}

- {sect ?

贡献:{sect.contribution}

: null} +

身份:{SECT_RANK_LABELS[rank]}

+

贡献:{data.sect.contribution}

+

本周工程:{data.overview?.project ? `${SECT_FACILITY_LABELS[data.overview.project.facilityKey]}升至 ${data.overview.project.targetLevel} 级` : '长老议定中'}

} + contentClassName="lg:max-w-none" > - -
- {tab === 'gate' ? ( - - ) : null} - {tab === 'methods' ? ( - - ) : null} - {tab === 'paths' ? ( - - ) : null} - {tab === 'commissions' ? ( - - ) : null} -
+

拖动舆图巡览群峰,滚轮或双指可缩放;聚焦设施即可查看其职司。

+ + + {({ zoomIn, zoomOut, resetTransform }) => ( +
+
+ zoomOut()} className="focus-visible:outline-crimson focus-visible:outline-2">缩小 + zoomIn()} className="focus-visible:outline-crimson focus-visible:outline-2">放大 + resetTransform()} className="focus-visible:outline-crimson focus-visible:outline-2">复位 +
+ +
+ 凌霄剑宗群峰、楼阁、灵脉矿场与药田的水墨鸟瞰图 + {SECT_MAP_HOTSPOTS.map((spot) => { + const facility = spot.facility ? facilities.get(spot.facility) : undefined; + const disabled = spot.locked || !spot.route; + return ( + + ); + })} +
+
+
+ )} +
); } diff --git a/src/react-app/routes/game/sect/spirit-vein/route.tsx b/src/react-app/routes/game/sect/spirit-vein/route.tsx new file mode 100644 index 00000000..c7abfa4c --- /dev/null +++ b/src/react-app/routes/game/sect/spirit-vein/route.tsx @@ -0,0 +1,39 @@ +import { SectPageLoading, SectScene, useSectCurrentData } from '../components/SectScene'; + +export default function SectSpiritVeinPage() { + const { data, error } = useSectCurrentData(); + if (!data) return ; + + const facility = data.overview?.facilities.find((item) => item.key === 'spirit_vein'); + const level = facility?.level ?? 1; + const bonus = level * 5; + + return ( +

灵脉等级:{level}级

俸禄灵石加成:+{bonus}%

} + > +
+
+
+
+

本级脉效

+ +{bonus}% +

只提高每周俸禄中的灵石部分,不改变任务、商店或捐献产生的灵石。

+
+ {Array.from({ length: 5 }, (_, index) => )} +
+
+
+
+ ); +} diff --git a/src/react-app/routes/game/sect/treasury/route.tsx b/src/react-app/routes/game/sect/treasury/route.tsx new file mode 100644 index 00000000..053fdfdf --- /dev/null +++ b/src/react-app/routes/game/sect/treasury/route.tsx @@ -0,0 +1,26 @@ +import { InkButton, InkCard } from '@app/components/ui'; +import { fetchSectShop } from '@app/lib/sect/sectClient'; +import type { SectShopData } from '@shared/contracts/sect'; +import { SECT_RANK_LABELS } from '@shared/engine/sect'; +import { useCallback, useEffect, useState } from 'react'; +import { postJson, SectPageLoading, SectScene, useSectMutation } from '../components/SectScene'; + +export default function SectTreasuryPage() { + const [shop, setShop] = useState(); + const reload = useCallback(async () => setShop(await fetchSectShop()), []); + useEffect(() => { + void fetchSectShop().then(setShop); + }, []); + const { busy, run } = useSectMutation(reload); + if (!shop) return ; + return ( +

当前贡献:{shop.contribution}

轮换周次:{shop.weekKey}

}> +
+ {shop.items.map((item) => { + const remaining = item.stock - item.purchased; + return
{item.name}{item.price} 贡献

{item.description}

身份
{SECT_RANK_LABELS[item.requiredRank]}
库存
{remaining} / {item.stock}
货架
{item.rotating ? '本周轮换' : '常驻'}
void run('/api/sects/current/shop/purchase', postJson({ itemId: item.id, quantity: 1, requestId: crypto.randomUUID() }), `已兑换「${item.name}」`)}>{remaining <= 0 ? '本周售罄' : '兑换一份'}
; + })} +
+
+ ); +} diff --git a/src/react-app/routes/game/sect/workshop/route.tsx b/src/react-app/routes/game/sect/workshop/route.tsx new file mode 100644 index 00000000..fa99ce23 --- /dev/null +++ b/src/react-app/routes/game/sect/workshop/route.tsx @@ -0,0 +1,5 @@ +import { Navigate } from 'react-router'; + +export default function SectWorkshopRedirect() { + return ; +} diff --git a/src/server/lib/drizzle/schema.ts b/src/server/lib/drizzle/schema.ts index 24d36d28..b5e2c47d 100644 --- a/src/server/lib/drizzle/schema.ts +++ b/src/server/lib/drizzle/schema.ts @@ -210,6 +210,11 @@ export const sectMemberships = pgTable( joinedAt: timestamp('joined_at'), activePathId: varchar('active_path_id', { length: 64 }), contribution: integer('contribution').notNull().default(0), + discipleRank: varchar('disciple_rank', { length: 16 }) + .notNull() + .default('registered'), + office: varchar('office', { length: 16 }).notNull().default('none'), + promotedAt: timestamp('promoted_at'), configVersion: integer('config_version').notNull().default(2), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at') @@ -229,6 +234,184 @@ export const sectMemberships = pgTable( ], ); +export const sectFacilities = pgTable( + 'wanjiedaoyou_sect_facilities', + { + id: uuid('id').primaryKey().defaultRandom(), + sectId: varchar('sect_id', { length: 64 }).notNull(), + facilityKey: varchar('facility_key', { length: 32 }).notNull(), + level: integer('level').notNull().default(1), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at') + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), + }, + (table) => [ + uniqueIndex('sect_facilities_sect_key_unique').on( + table.sectId, + table.facilityKey, + ), + ], +); + +export const sectConstructionProjects = pgTable( + 'wanjiedaoyou_sect_construction_projects', + { + id: uuid('id').primaryKey().defaultRandom(), + sectId: varchar('sect_id', { length: 64 }).notNull(), + facilityKey: varchar('facility_key', { length: 32 }).notNull(), + targetLevel: integer('target_level').notNull(), + progress: integer('progress').notNull().default(0), + target: integer('target').notNull(), + status: varchar('status', { length: 16 }).notNull().default('active'), + startedWeekKey: varchar('started_week_key', { length: 10 }).notNull(), + completedAt: timestamp('completed_at'), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at') + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), + }, + (table) => [ + uniqueIndex('sect_projects_active_sect_unique') + .on(table.sectId) + .where(sql`${table.status} = 'active'`), + index('sect_projects_sect_created_idx').on(table.sectId, table.createdAt), + ], +); + +export const sectTaskRecords = pgTable( + 'wanjiedaoyou_sect_task_records', + { + id: uuid('id').primaryKey().defaultRandom(), + membershipId: uuid('membership_id') + .references(() => sectMemberships.id, { onDelete: 'cascade' }) + .notNull(), + taskId: varchar('task_id', { length: 64 }).notNull(), + kind: varchar('kind', { length: 16 }).notNull(), + periodKey: varchar('period_key', { length: 16 }).notNull(), + status: varchar('status', { length: 16 }).notNull().default('active'), + progress: integer('progress').notNull().default(0), + payload: jsonb('payload').notNull().default({}), + completedAt: timestamp('completed_at'), + claimedAt: timestamp('claimed_at'), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at') + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), + }, + (table) => [ + uniqueIndex('sect_task_membership_period_task_unique').on( + table.membershipId, + table.periodKey, + table.taskId, + ), + index('sect_task_membership_kind_period_idx').on( + table.membershipId, + table.kind, + table.periodKey, + ), + ], +); + +export const sectContributionLedger = pgTable( + 'wanjiedaoyou_sect_contribution_ledger', + { + id: uuid('id').primaryKey().defaultRandom(), + membershipId: uuid('membership_id') + .references(() => sectMemberships.id, { onDelete: 'cascade' }) + .notNull(), + delta: integer('delta').notNull(), + balanceAfter: integer('balance_after').notNull(), + source: varchar('source', { length: 32 }).notNull(), + referenceId: varchar('reference_id', { length: 128 }), + createdAt: timestamp('created_at').notNull().defaultNow(), + }, + (table) => [ + index('sect_contribution_membership_created_idx').on( + table.membershipId, + table.createdAt, + ), + ], +); + +export const sectDonationLedger = pgTable( + 'wanjiedaoyou_sect_donation_ledger', + { + id: uuid('id').primaryKey().defaultRandom(), + membershipId: uuid('membership_id') + .references(() => sectMemberships.id, { onDelete: 'cascade' }) + .notNull(), + projectId: uuid('project_id') + .references(() => sectConstructionProjects.id, { onDelete: 'restrict' }) + .notNull(), + dateKey: varchar('date_key', { length: 10 }).notNull(), + demandId: varchar('demand_id', { length: 64 }).notNull(), + contribution: integer('contribution').notNull(), + constructionPoints: integer('construction_points').notNull(), + itemSnapshot: jsonb('item_snapshot').notNull().default({}), + requestId: varchar('request_id', { length: 128 }), + createdAt: timestamp('created_at').notNull().defaultNow(), + }, + (table) => [ + index('sect_donation_member_date_idx').on(table.membershipId, table.dateKey), + uniqueIndex('sect_donation_member_request_unique') + .on(table.membershipId, table.requestId) + .where(sql`${table.requestId} is not null`), + ], +); + +export const sectShopPurchases = pgTable( + 'wanjiedaoyou_sect_shop_purchases', + { + id: uuid('id').primaryKey().defaultRandom(), + membershipId: uuid('membership_id') + .references(() => sectMemberships.id, { onDelete: 'cascade' }) + .notNull(), + weekKey: varchar('week_key', { length: 10 }).notNull(), + itemId: varchar('item_id', { length: 64 }).notNull(), + quantity: integer('quantity').notNull().default(1), + requestId: varchar('request_id', { length: 128 }), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at') + .notNull() + .defaultNow() + .$onUpdate(() => new Date()), + }, + (table) => [ + index('sect_shop_member_week_item_idx').on( + table.membershipId, + table.weekKey, + table.itemId, + ), + uniqueIndex('sect_shop_member_request_unique') + .on(table.membershipId, table.requestId) + .where(sql`${table.requestId} is not null`), + ], +); + +export const sectStipendClaims = pgTable( + 'wanjiedaoyou_sect_stipend_claims', + { + id: uuid('id').primaryKey().defaultRandom(), + membershipId: uuid('membership_id') + .references(() => sectMemberships.id, { onDelete: 'cascade' }) + .notNull(), + weekKey: varchar('week_key', { length: 10 }).notNull(), + spiritStones: integer('spirit_stones').notNull(), + rewards: jsonb('rewards').notNull().default([]), + claimedAt: timestamp('claimed_at').notNull().defaultNow(), + }, + (table) => [ + uniqueIndex('sect_stipend_member_week_unique').on( + table.membershipId, + table.weekKey, + ), + ], +); + export const sectMethodProgress = pgTable( 'wanjiedaoyou_sect_method_progress', { diff --git a/src/server/lib/jobs/internalCron.test.ts b/src/server/lib/jobs/internalCron.test.ts index 93d8296a..4008d048 100644 --- a/src/server/lib/jobs/internalCron.test.ts +++ b/src/server/lib/jobs/internalCron.test.ts @@ -11,6 +11,8 @@ const { generateDailyMarketMaterialLibraryEntriesMock, redisMock, transactionMock, + listActiveSectIdsMock, + ensureWeeklyProjectMock, } = vi.hoisted(() => { const tx = { select: vi.fn(), @@ -45,6 +47,8 @@ const { transactionMock: tx, getItemLibraryDailyMaterialGenerationSettingsMock: vi.fn(), generateDailyMarketMaterialLibraryEntriesMock: vi.fn(), + listActiveSectIdsMock: vi.fn(), + ensureWeeklyProjectMock: vi.fn(), }; }); @@ -64,6 +68,16 @@ vi.mock('@server/lib/repositories/playerStateRepository', () => ({ prunePlayerStateEventsOlderThan: prunePlayerStateEventsOlderThanMock, })); +vi.mock('@server/lib/repositories/sectOrganizationRepository', () => ({ + listActiveSectIds: listActiveSectIdsMock, +})); + +vi.mock('@server/lib/services/SectOrganizationService', () => ({ + SectOrganizationService: { + ensureWeeklyProject: ensureWeeklyProjectMock, + }, +})); + vi.mock('@server/lib/repositories/retentionRepository', () => ({ pruneExpiredData: pruneExpiredDataMock, })); @@ -112,6 +126,7 @@ import { runMaterialLibraryDailyGenerationJob, runPlayerStateEventsCleanupJob, runRankRewardsJob, + runSectConstructionWeeklyJob, } from './internalCron'; describe('internal cron jobs', () => { @@ -132,6 +147,7 @@ describe('internal cron jobs', () => { sendMailMock.mockImplementation(async (cultivatorId: string) => ({ id: `mail-${cultivatorId}`, })); + listActiveSectIdsMock.mockResolvedValue(['lingxiao']); transactionMock.select.mockReturnValue({ from: vi.fn(() => ({ where: vi.fn(async () => [{ count: 1 }]), @@ -148,6 +164,20 @@ describe('internal cron jobs', () => { }); }); + it('opens or continues one shared sect project on Monday in Shanghai', async () => { + const result = await runSectConstructionWeeklyJob(); + + expect(result).toMatchObject({ + success: true, + processed: 1, + skipped: false, + }); + expect(ensureWeeklyProjectMock).toHaveBeenCalledWith( + 'lingxiao', + expect.anything(), + ); + }); + afterEach(() => { vi.useRealTimers(); }); diff --git a/src/server/lib/jobs/internalCron.ts b/src/server/lib/jobs/internalCron.ts index 98eab41f..ffa179e3 100644 --- a/src/server/lib/jobs/internalCron.ts +++ b/src/server/lib/jobs/internalCron.ts @@ -7,6 +7,7 @@ import { cultivators, mails } from '@server/lib/drizzle/schema'; import { redis } from '@server/lib/redis'; import { getTopRankingCultivatorIds } from '@server/lib/redis/rankings'; import { prunePlayerStateEventsOlderThan } from '@server/lib/repositories/playerStateRepository'; +import { listActiveSectIds } from '@server/lib/repositories/sectOrganizationRepository'; import { pruneExpiredData, type ExpiredDataCleanupResult, @@ -24,6 +25,7 @@ import { ITEM_LIBRARY_SYSTEM_USER_ID, } from '@server/lib/services/MaterialLibraryService'; import { commitPlayerStateMutation } from '@server/lib/services/PlayerStateMutationService'; +import { SectOrganizationService } from '@server/lib/services/SectOrganizationService'; import { towerEnemySetService } from '@server/lib/tower/enemySets'; import { RANKING_REWARDS, REALM_VALUES } from '@shared/types/constants'; import { and, eq, sql } from 'drizzle-orm'; @@ -38,6 +40,7 @@ const PLAYER_STATE_EVENTS_CLEANUP_LOCK_KEY = const EXPIRED_DATA_CLEANUP_LOCK_KEY = 'cron:expired-data-cleanup:lock'; const MATERIAL_LIBRARY_DAILY_GENERATION_LOCK_KEY = 'cron:material-library-daily-generation:lock'; +const SECT_CONSTRUCTION_WEEKLY_LOCK_KEY = 'cron:sect-construction-weekly:lock'; const RANK_REWARD_SETTLED_PREFIX = 'golden_rank:weekly_rewards:settled:'; const LOCK_TTL_SECONDS = 15 * 60; const TOWER_ENEMY_SETS_LOCK_TTL_SECONDS = 2 * 60 * 60; @@ -346,6 +349,32 @@ export async function runMarketRefreshCronJob(): Promise { }); } +export async function runSectConstructionWeeklyJob(): Promise { + return withJobLock( + 'sect-construction-weekly', + SECT_CONSTRUCTION_WEEKLY_LOCK_KEY, + async () => { + if (!isSettlementMondayCN()) { + return { + success: true, + processed: 0, + skipped: true, + reason: 'not_monday_cn', + }; + } + const q = getExecutor(); + const sectIds = await listActiveSectIds(q); + for (const sectId of sectIds) + await SectOrganizationService.ensureWeeklyProject(sectId, q); + return { + success: true, + processed: sectIds.length, + skipped: false, + }; + }, + ); +} + export async function runMaterialLibraryDailyGenerationJob(): Promise { return withJobLock( 'material-library-daily-generation', diff --git a/src/server/lib/jobs/internalCronScheduler.ts b/src/server/lib/jobs/internalCronScheduler.ts index 95f97b2b..ed666d3d 100644 --- a/src/server/lib/jobs/internalCronScheduler.ts +++ b/src/server/lib/jobs/internalCronScheduler.ts @@ -6,6 +6,7 @@ import { runMaterialLibraryDailyGenerationJob, runPlayerStateEventsCleanupJob, runRankRewardsJob, + runSectConstructionWeeklyJob, runTowerEnemySetRefreshJob, } from './internalCron'; @@ -24,6 +25,8 @@ const AUCTION_EXPIRE_SCHEDULE = '*/2 * * * *'; const BET_BATTLE_EXPIRE_SCHEDULE = '*/2 * * * *'; // Bun.cron uses UTC for cron expressions. 16:00 UTC equals 00:00 Asia/Shanghai. const RANK_REWARDS_SCHEDULE = '0 16 * * *'; +// Sunday 16:00 UTC equals Monday 00:00 Asia/Shanghai. +const SECT_CONSTRUCTION_WEEKLY_SCHEDULE = '0 16 * * 0'; // Market refresh: every 5 minutes to pre-generate listings before 15-min cycle ends const MARKET_REFRESH_SCHEDULE = '*/5 * * * *'; const TOWER_ENEMY_SETS_SCHEDULE = '0 * * * *'; @@ -79,6 +82,12 @@ export function registerInternalCronJobs(options: { bunCron(RANK_REWARDS_SCHEDULE, () => runScheduledJob('rank-rewards', runRankRewardsJob), ), + bunCron(SECT_CONSTRUCTION_WEEKLY_SCHEDULE, () => + runScheduledJob( + 'sect-construction-weekly', + runSectConstructionWeeklyJob, + ), + ), bunCron(MARKET_REFRESH_SCHEDULE, () => runScheduledJob('market-refresh', runMarketRefreshCronJob), ), @@ -108,6 +117,8 @@ export function registerInternalCronJobs(options: { betBattleExpire: BET_BATTLE_EXPIRE_SCHEDULE, rankRewardsUtc: RANK_REWARDS_SCHEDULE, rankRewardsLocal: '00:00 Asia/Shanghai', + sectConstructionWeeklyUtc: SECT_CONSTRUCTION_WEEKLY_SCHEDULE, + sectConstructionWeeklyLocal: 'Monday 00:00 Asia/Shanghai', marketRefresh: MARKET_REFRESH_SCHEDULE, towerEnemySets: TOWER_ENEMY_SETS_SCHEDULE, playerStateEventsCleanupUtc: PLAYER_STATE_EVENTS_CLEANUP_SCHEDULE, diff --git a/src/server/lib/repositories/sectOrganizationRepository.ts b/src/server/lib/repositories/sectOrganizationRepository.ts new file mode 100644 index 00000000..5173b467 --- /dev/null +++ b/src/server/lib/repositories/sectOrganizationRepository.ts @@ -0,0 +1,787 @@ +import type { DbExecutor, DbTransaction } from '@server/lib/drizzle/db'; +import { + creationProducts, + cultivators, + materials, + consumables, + sectConstructionProjects, + sectContributionLedger, + sectDonationLedger, + sectFacilities, + sectMemberships, + sectShopPurchases, + sectStipendClaims, + sectTaskRecords, +} from '@server/lib/drizzle/schema'; +import type { + SectDiscipleRank, + SectFacilityKey, + SectOffice, + UpgradeableSectFacilityKey, +} from '@shared/engine/sect'; +import { and, asc, count, desc, eq, gte, sql } from 'drizzle-orm'; + +const INITIAL_FACILITIES: SectFacilityKey[] = [ + 'archive', + 'cultivation_room', + 'workshop', + 'spirit_vein', + 'herb_garden', + 'formation', +]; + +export async function ensureSectFacilities( + sectId: string, + q: DbExecutor | DbTransaction, +) { + await q + .insert(sectFacilities) + .values(INITIAL_FACILITIES.map((facilityKey) => ({ sectId, facilityKey }))) + .onConflictDoNothing(); +} + +export async function listActiveSectIds(q: DbExecutor | DbTransaction) { + const rows = await q + .selectDistinct({ sectId: sectMemberships.sectId }) + .from(sectMemberships) + .where(eq(sectMemberships.status, 'active')); + return rows.map((row) => row.sectId); +} + +export async function listSectFacilities( + sectId: string, + q: DbExecutor | DbTransaction, +) { + return q + .select() + .from(sectFacilities) + .where(eq(sectFacilities.sectId, sectId)) + .orderBy(asc(sectFacilities.facilityKey)); +} + +export async function findActiveSectProject( + sectId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectConstructionProjects) + .where( + and( + eq(sectConstructionProjects.sectId, sectId), + eq(sectConstructionProjects.status, 'active'), + ), + ) + .limit(1); + return row ?? null; +} + +export async function findLatestCompletedSectProject( + sectId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectConstructionProjects) + .where( + and( + eq(sectConstructionProjects.sectId, sectId), + eq(sectConstructionProjects.status, 'completed'), + ), + ) + .orderBy(desc(sectConstructionProjects.completedAt)) + .limit(1); + return row ?? null; +} + +export async function createSectProject( + input: { + sectId: string; + facilityKey: UpgradeableSectFacilityKey; + targetLevel: number; + target: number; + startedWeekKey: string; + }, + q: DbExecutor | DbTransaction, +) { + await q.insert(sectConstructionProjects).values(input).onConflictDoNothing(); + return findActiveSectProject(input.sectId, q); +} + +export async function countRecentlyActiveSectMembers( + sectId: string, + since: Date, + q: DbExecutor | DbTransaction, +): Promise { + const [row] = await q + .select({ value: count() }) + .from(sectMemberships) + .innerJoin(cultivators, eq(cultivators.id, sectMemberships.cultivatorId)) + .where( + and( + eq(sectMemberships.sectId, sectId), + eq(sectMemberships.status, 'active'), + gte(cultivators.updatedAt, since), + ), + ); + return Number(row?.value ?? 0); +} + +export async function advanceSectProject( + projectId: string, + points: number, + tx: DbTransaction, +) { + const [advanced] = await tx + .update(sectConstructionProjects) + .set({ + progress: sql`LEAST(${sectConstructionProjects.target}, ${sectConstructionProjects.progress} + ${points})`, + updatedAt: new Date(), + }) + .where( + and( + eq(sectConstructionProjects.id, projectId), + eq(sectConstructionProjects.status, 'active'), + ), + ) + .returning(); + if (!advanced) return null; + if (advanced.progress < advanced.target) return advanced; + + const [completed] = await tx + .update(sectConstructionProjects) + .set({ status: 'completed', completedAt: new Date(), updatedAt: new Date() }) + .where( + and( + eq(sectConstructionProjects.id, projectId), + eq(sectConstructionProjects.status, 'active'), + ), + ) + .returning(); + if (completed) { + await tx + .update(sectFacilities) + .set({ level: completed.targetLevel, updatedAt: new Date() }) + .where( + and( + eq(sectFacilities.sectId, completed.sectId), + eq(sectFacilities.facilityKey, completed.facilityKey), + sql`${sectFacilities.level} < ${completed.targetLevel}`, + ), + ); + } + return completed ?? advanced; +} + +export async function addSectContribution( + membershipId: string, + amount: number, + source: string, + referenceId: string | null, + tx: DbTransaction, +) { + const [membership] = await tx + .update(sectMemberships) + .set({ + contribution: sql`${sectMemberships.contribution} + ${amount}`, + updatedAt: new Date(), + }) + .where(eq(sectMemberships.id, membershipId)) + .returning({ contribution: sectMemberships.contribution }); + if (!membership) throw new Error('宗门成员不存在'); + await tx.insert(sectContributionLedger).values({ + membershipId, + delta: amount, + balanceAfter: membership.contribution, + source, + referenceId, + }); + return membership.contribution; +} + +export async function spendSectContribution( + membershipId: string, + amount: number, + source: string, + referenceId: string | null, + tx: DbTransaction, +) { + const [membership] = await tx + .update(sectMemberships) + .set({ + contribution: sql`${sectMemberships.contribution} - ${amount}`, + updatedAt: new Date(), + }) + .where( + and( + eq(sectMemberships.id, membershipId), + sql`${sectMemberships.contribution} >= ${amount}`, + ), + ) + .returning({ contribution: sectMemberships.contribution }); + if (!membership) return null; + await tx.insert(sectContributionLedger).values({ + membershipId, + delta: -amount, + balanceAfter: membership.contribution, + source, + referenceId, + }); + return membership.contribution; +} + +export async function promoteSectMembership( + membershipId: string, + rank: SectDiscipleRank, + tx: DbTransaction, +) { + const [row] = await tx + .update(sectMemberships) + .set({ discipleRank: rank, promotedAt: new Date(), updatedAt: new Date() }) + .where(eq(sectMemberships.id, membershipId)) + .returning(); + return row ?? null; +} + +export async function listSectTaskRecords( + membershipId: string, + q: DbExecutor | DbTransaction, +) { + return q + .select() + .from(sectTaskRecords) + .where(eq(sectTaskRecords.membershipId, membershipId)) + .orderBy(desc(sectTaskRecords.createdAt)); +} + +export async function findSectTaskRecord( + membershipId: string, + periodKey: string, + taskId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectTaskRecords) + .where( + and( + eq(sectTaskRecords.membershipId, membershipId), + eq(sectTaskRecords.periodKey, periodKey), + eq(sectTaskRecords.taskId, taskId), + ), + ) + .limit(1); + return row ?? null; +} + +export async function findDailySectTask( + membershipId: string, + dateKey: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectTaskRecords) + .where( + and( + eq(sectTaskRecords.membershipId, membershipId), + eq(sectTaskRecords.periodKey, dateKey), + eq(sectTaskRecords.kind, 'daily'), + ), + ) + .limit(1); + return row ?? null; +} + +export async function createSectTaskRecord( + input: { + membershipId: string; + taskId: string; + kind: 'daily' | 'weekly' | 'promotion'; + periodKey: string; + progress?: number; + payload?: Record; + }, + tx: DbTransaction, +) { + const [row] = await tx + .insert(sectTaskRecords) + .values({ ...input, progress: input.progress ?? 0, payload: input.payload ?? {} }) + .onConflictDoNothing() + .returning(); + return row ?? findSectTaskRecord(input.membershipId, input.periodKey, input.taskId, tx); +} + +export async function completeSectTaskRecord( + id: string, + progress: number, + tx: DbTransaction, +) { + const [row] = await tx + .update(sectTaskRecords) + .set({ + status: 'completed', + progress, + completedAt: new Date(), + updatedAt: new Date(), + }) + .where( + and(eq(sectTaskRecords.id, id), eq(sectTaskRecords.status, 'active')), + ) + .returning(); + return row ?? null; +} + +export async function upsertSectTaskProgress( + input: { + membershipId: string; + taskId: string; + kind: 'weekly' | 'promotion'; + periodKey: string; + progress: number; + target: number; + payload?: Record; + }, + tx: DbTransaction, +) { + const completed = input.progress >= input.target; + const [row] = await tx + .insert(sectTaskRecords) + .values({ + membershipId: input.membershipId, + taskId: input.taskId, + kind: input.kind, + periodKey: input.periodKey, + progress: input.progress, + payload: input.payload ?? { target: input.target }, + status: completed ? 'completed' : 'active', + completedAt: completed ? new Date() : null, + }) + .onConflictDoUpdate({ + target: [ + sectTaskRecords.membershipId, + sectTaskRecords.periodKey, + sectTaskRecords.taskId, + ], + set: { + progress: input.progress, + payload: input.payload ?? { target: input.target }, + status: completed ? 'completed' : 'active', + completedAt: completed ? new Date() : null, + updatedAt: new Date(), + }, + }) + .returning(); + return row; +} + +export async function countCompletedDailySectTasks( + membershipId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ value: count() }) + .from(sectTaskRecords) + .where( + and( + eq(sectTaskRecords.membershipId, membershipId), + eq(sectTaskRecords.kind, 'daily'), + eq(sectTaskRecords.status, 'completed'), + ), + ); + return Number(row?.value ?? 0); +} + +export async function countCompletedDailySectTasksSince( + membershipId: string, + periodKey: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ value: count() }) + .from(sectTaskRecords) + .where( + and( + eq(sectTaskRecords.membershipId, membershipId), + eq(sectTaskRecords.kind, 'daily'), + eq(sectTaskRecords.status, 'completed'), + gte(sectTaskRecords.periodKey, periodKey), + ), + ); + return Number(row?.value ?? 0); +} + +export async function hasCompletedSectTask( + membershipId: string, + taskId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ id: sectTaskRecords.id }) + .from(sectTaskRecords) + .where( + and( + eq(sectTaskRecords.membershipId, membershipId), + eq(sectTaskRecords.taskId, taskId), + eq(sectTaskRecords.status, 'completed'), + ), + ) + .limit(1); + return Boolean(row); +} + +export async function getPurchasedSectShopQuantity( + membershipId: string, + weekKey: string, + itemId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ quantity: sql`COALESCE(SUM(${sectShopPurchases.quantity}), 0)` }) + .from(sectShopPurchases) + .where( + and( + eq(sectShopPurchases.membershipId, membershipId), + eq(sectShopPurchases.weekKey, weekKey), + eq(sectShopPurchases.itemId, itemId), + ), + ) + .limit(1); + return Number(row?.quantity ?? 0); +} + +export async function addSectShopPurchase( + membershipId: string, + weekKey: string, + itemId: string, + quantity: number, + requestId: string | undefined, + tx: DbTransaction, +) { + const [row] = await tx + .insert(sectShopPurchases) + .values({ membershipId, weekKey, itemId, quantity, requestId }) + .onConflictDoNothing() + .returning(); + return row ?? null; +} + +export async function findSectShopPurchaseByRequestId( + membershipId: string, + requestId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectShopPurchases) + .where( + and( + eq(sectShopPurchases.membershipId, membershipId), + eq(sectShopPurchases.requestId, requestId), + ), + ) + .limit(1); + return row ?? null; +} + +export async function hasClaimedSectStipend( + membershipId: string, + weekKey: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ id: sectStipendClaims.id }) + .from(sectStipendClaims) + .where( + and( + eq(sectStipendClaims.membershipId, membershipId), + eq(sectStipendClaims.weekKey, weekKey), + ), + ) + .limit(1); + return Boolean(row); +} + +export async function createSectStipendClaim( + input: { + membershipId: string; + weekKey: string; + spiritStones: number; + rewards: unknown[]; + }, + tx: DbTransaction, +) { + const [row] = await tx + .insert(sectStipendClaims) + .values(input) + .onConflictDoNothing() + .returning(); + return row ?? null; +} + +export async function addCultivatorSpiritStones( + cultivatorId: string, + amount: number, + tx: DbTransaction, +) { + await tx + .update(cultivators) + .set({ spirit_stones: sql`${cultivators.spirit_stones} + ${amount}` }) + .where(eq(cultivators.id, cultivatorId)); +} + +export async function spendCultivatorSpiritStones( + cultivatorId: string, + amount: number, + tx: DbTransaction, +) { + const [row] = await tx + .update(cultivators) + .set({ spirit_stones: sql`${cultivators.spirit_stones} - ${amount}` }) + .where( + and( + eq(cultivators.id, cultivatorId), + sql`${cultivators.spirit_stones} >= ${amount}`, + ), + ) + .returning({ id: cultivators.id }); + return Boolean(row); +} + +export async function findOwnedMaterial( + cultivatorId: string, + itemId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(materials) + .where(and(eq(materials.cultivatorId, cultivatorId), eq(materials.id, itemId))) + .limit(1); + return row ?? null; +} + +export async function findOwnedConsumable( + cultivatorId: string, + itemId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(consumables) + .where( + and(eq(consumables.cultivatorId, cultivatorId), eq(consumables.id, itemId)), + ) + .limit(1); + return row ?? null; +} + +export async function findOwnedArtifact( + cultivatorId: string, + itemId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(creationProducts) + .where( + and( + eq(creationProducts.cultivatorId, cultivatorId), + eq(creationProducts.id, itemId), + eq(creationProducts.productType, 'artifact'), + ), + ) + .limit(1); + return row ?? null; +} + +export async function consumeOwnedMaterial( + itemId: string, + quantity: number, + tx: DbTransaction, +) { + const [row] = await tx.select().from(materials).where(eq(materials.id, itemId)).limit(1); + if (!row || row.quantity < quantity) return false; + if (row.quantity === quantity) await tx.delete(materials).where(eq(materials.id, itemId)); + else + await tx + .update(materials) + .set({ quantity: row.quantity - quantity }) + .where(eq(materials.id, itemId)); + return true; +} + +export async function consumeOwnedConsumable( + itemId: string, + quantity: number, + tx: DbTransaction, +) { + const [row] = await tx + .select() + .from(consumables) + .where(eq(consumables.id, itemId)) + .limit(1); + if (!row || row.quantity < quantity) return false; + if (row.quantity === quantity) + await tx.delete(consumables).where(eq(consumables.id, itemId)); + else + await tx + .update(consumables) + .set({ quantity: row.quantity - quantity }) + .where(eq(consumables.id, itemId)); + return true; +} + +export async function consumeOwnedArtifact(itemId: string, tx: DbTransaction) { + const rows = await tx + .delete(creationProducts) + .where( + and( + eq(creationProducts.id, itemId), + eq(creationProducts.productType, 'artifact'), + eq(creationProducts.isEquipped, false), + ), + ) + .returning({ id: creationProducts.id }); + return rows.length === 1; +} + +export async function sumSectDonationContributionForDate( + membershipId: string, + dateKey: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ value: sql`COALESCE(SUM(${sectDonationLedger.contribution}), 0)` }) + .from(sectDonationLedger) + .where( + and( + eq(sectDonationLedger.membershipId, membershipId), + eq(sectDonationLedger.dateKey, dateKey), + ), + ); + return Number(row?.value ?? 0); +} + +export async function insertSectDonation( + input: { + membershipId: string; + projectId: string; + dateKey: string; + demandId: string; + contribution: number; + constructionPoints: number; + itemSnapshot: Record; + requestId?: string; + }, + tx: DbTransaction, +) { + const [row] = await tx + .insert(sectDonationLedger) + .values(input) + .onConflictDoNothing() + .returning(); + return row ?? null; +} + +export async function findSectDonationByRequestId( + membershipId: string, + requestId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select() + .from(sectDonationLedger) + .where( + and( + eq(sectDonationLedger.membershipId, membershipId), + eq(sectDonationLedger.requestId, requestId), + ), + ) + .limit(1); + return row ?? null; +} + +export async function listRecentSectDonations( + sectId: string, + limit: number, + q: DbExecutor | DbTransaction, +) { + return q + .select({ + id: sectDonationLedger.id, + memberName: cultivators.name, + demandId: sectDonationLedger.demandId, + contribution: sectDonationLedger.contribution, + constructionPoints: sectDonationLedger.constructionPoints, + createdAt: sectDonationLedger.createdAt, + }) + .from(sectDonationLedger) + .innerJoin( + sectMemberships, + eq(sectMemberships.id, sectDonationLedger.membershipId), + ) + .innerJoin(cultivators, eq(cultivators.id, sectMemberships.cultivatorId)) + .where(eq(sectMemberships.sectId, sectId)) + .orderBy(desc(sectDonationLedger.createdAt)) + .limit(limit); +} + +export async function listSectMembers( + sectId: string, + page: number, + pageSize: number, + q: DbExecutor | DbTransaction, +) { + const where = and( + eq(sectMemberships.sectId, sectId), + eq(sectMemberships.status, 'active'), + ); + const [totalRow, rows] = await Promise.all([ + q.select({ value: count() }).from(sectMemberships).where(where), + q + .select({ + cultivatorId: cultivators.id, + name: cultivators.name, + realm: cultivators.realm, + realmStage: cultivators.realm_stage, + discipleRank: sectMemberships.discipleRank, + office: sectMemberships.office, + joinedAt: sectMemberships.joinedAt, + }) + .from(sectMemberships) + .innerJoin(cultivators, eq(cultivators.id, sectMemberships.cultivatorId)) + .where(where) + .orderBy(desc(sectMemberships.promotedAt), asc(cultivators.name)) + .limit(pageSize) + .offset((page - 1) * pageSize), + ]); + return { + rows: rows.map((row) => ({ + ...row, + discipleRank: row.discipleRank as SectDiscipleRank, + office: row.office as SectOffice, + })), + total: Number(totalRow[0]?.value ?? 0), + }; +} + +export async function findSectMirrorCultivatorId( + sectId: string, + excludeCultivatorId: string, + q: DbExecutor | DbTransaction, +) { + const [row] = await q + .select({ cultivatorId: sectMemberships.cultivatorId }) + .from(sectMemberships) + .where( + and( + eq(sectMemberships.sectId, sectId), + eq(sectMemberships.status, 'active'), + sql`${sectMemberships.cultivatorId} <> ${excludeCultivatorId}`, + ), + ) + .orderBy(asc(sectMemberships.cultivatorId)) + .limit(1); + return row?.cultivatorId ?? null; +} diff --git a/src/server/lib/repositories/sectRepository.ts b/src/server/lib/repositories/sectRepository.ts index 1f0adaba..c2934c66 100644 --- a/src/server/lib/repositories/sectRepository.ts +++ b/src/server/lib/repositories/sectRepository.ts @@ -176,6 +176,9 @@ async function hydrateMembership( joinedAt: membership.joinedAt?.toISOString(), activePathId: membership.activePathId ?? undefined, contribution: membership.contribution, + discipleRank: membership.discipleRank as CultivatorSectState['discipleRank'], + office: membership.office as CultivatorSectState['office'], + promotedAt: membership.promotedAt?.toISOString(), configVersion: membership.configVersion, methods: Object.fromEntries( methods.map((row) => [row.methodId, row.level]), diff --git a/src/server/lib/services/AlchemyFormulaService.ts b/src/server/lib/services/AlchemyFormulaService.ts index 8fae9b9f..966f20f0 100644 --- a/src/server/lib/services/AlchemyFormulaService.ts +++ b/src/server/lib/services/AlchemyFormulaService.ts @@ -96,6 +96,7 @@ import { getPlayerRuntimeCultivatorByIdUnsafe, } from './cultivatorService'; import { getMysteryMaterialBlockingReason } from './materialMysteryGuard'; +import { SectOrganizationService } from './SectOrganizationService'; const DISCOVERY_TTL_SECONDS = 600; const FORMULA_ANALYSIS_TTL_SECONDS = 600; @@ -1315,10 +1316,14 @@ export async function previewFormulaCraft( const highestMaterialRank = calculateHighestMaterialRank( rows as Array<{ rank: Quality }>, ); - const spiritStones = scaleFateAdjustedValue( + const baseSpiritStones = scaleFateAdjustedValue( calculateCraftCost(highestMaterialRank, 'spiritStone'), getAlchemySpiritStoneMultiplier(evaluateFateContext(fates)), ); + const spiritStones = await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseSpiritStones, + ); return { cost: { spiritStones }, @@ -1412,12 +1417,17 @@ export async function craftFromFormula( const highestMaterialRank = calculateHighestMaterialRank( selectedMaterials as Array<{ rank: Quality }>, ); - const cost = scaleFateAdjustedValue( + const baseCost = scaleFateAdjustedValue( calculateCraftCost(highestMaterialRank, 'spiritStone'), getAlchemySpiritStoneMultiplier( evaluateFateContext(fullCultivator?.cultivator.pre_heaven_fates ?? []), ), ); + const cost = await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseCost, + options.tx ?? getExecutor(), + ); if ((cultivator.spirit_stones ?? 0) < cost) { throw new AlchemyServiceError(`灵石不足,需要 ${cost} 枚`); } diff --git a/src/server/lib/services/SectError.ts b/src/server/lib/services/SectError.ts new file mode 100644 index 00000000..cd9eb6f1 --- /dev/null +++ b/src/server/lib/services/SectError.ts @@ -0,0 +1,24 @@ +export type SectErrorCode = + | 'SECT_UNKNOWN' + | 'SECT_TRIAL_REQUIRED' + | 'SECT_ALREADY_JOINED' + | 'SECT_REALM_GATE' + | 'SECT_METHOD_CAP' + | 'SECT_PATH_UNKNOWN' + | 'SECT_PATH_NOT_LEARNED' + | 'SECT_PATH_ALREADY_LEARNED' + | 'SECT_INSUFFICIENT_RESOURCES' + | 'SECT_INVALID_MERIDIAN' + | 'SECT_INVALID_LOADOUT' + | 'SECT_COMMISSION_ALREADY_CLAIMED' + | 'SECT_ORGANIZATION_INVALID'; + +export class SectError extends Error { + constructor( + public readonly code: SectErrorCode, + message: string, + public readonly status = 409, + ) { + super(message); + } +} diff --git a/src/server/lib/services/SectFacilityService.ts b/src/server/lib/services/SectFacilityService.ts new file mode 100644 index 00000000..8bb81d71 --- /dev/null +++ b/src/server/lib/services/SectFacilityService.ts @@ -0,0 +1,36 @@ +import type { DbExecutor, DbTransaction } from '@server/lib/drizzle/db'; +import * as organizationRepository from '@server/lib/repositories/sectOrganizationRepository'; +import * as sectRepository from '@server/lib/repositories/sectRepository'; +import { + getSectCraftDiscount, + getSectFacilityBonus, + type SectDiscipleRank, +} from '@shared/engine/sect'; + +export async function getSectFacilityBonuses( + cultivatorId: string, + q: DbExecutor | DbTransaction, +) { + const membership = await sectRepository.findMembership(cultivatorId, q); + if (!membership) + return { retreatMultiplier: 1, craftDiscount: 0, archiveLevel: 1 }; + const facilities = await organizationRepository.listSectFacilities( + membership.sectId, + q, + ); + const level = (key: string) => + facilities.find((item) => item.facilityKey === key)?.level ?? 1; + return { + retreatMultiplier: + 1 + + getSectFacilityBonus( + 'cultivation_room', + level('cultivation_room'), + ), + craftDiscount: getSectCraftDiscount( + membership.discipleRank as SectDiscipleRank, + level('workshop'), + ), + archiveLevel: level('archive'), + }; +} diff --git a/src/server/lib/services/SectOrganizationConfig.test.ts b/src/server/lib/services/SectOrganizationConfig.test.ts new file mode 100644 index 00000000..38c1fbc7 --- /dev/null +++ b/src/server/lib/services/SectOrganizationConfig.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; +import { + getSectBountyMode, + getSectDateKey, + getSectDonationDemands, + getSectWeekKey, +} from './SectOrganizationConfig'; + +describe('宗门日周配置', () => { + it('使用 Asia/Shanghai 生成日期与周一键', () => { + expect(getSectDateKey(new Date('2026-07-19T16:30:00.000Z'))).toBe('2026-07-20'); + expect(getSectWeekKey(new Date('2026-07-19T16:30:00.000Z'))).toBe('2026-07-20'); + expect(getSectWeekKey(new Date('2026-07-26T15:59:59.000Z'))).toBe('2026-07-20'); + }); + + it('同一周悬赏模式稳定,相邻周发生轮换', () => { + expect(getSectBountyMode('2026-07-20')).toBe(getSectBountyMode('2026-07-20')); + expect(getSectBountyMode('2026-07-20')).not.toBe(getSectBountyMode('2026-07-27')); + }); + + it('同一日期需求确定且法宝与丹药按日轮换', () => { + expect(getSectDonationDemands('lingxiao', '2026-07-20')).toEqual( + getSectDonationDemands('lingxiao', '2026-07-20'), + ); + expect(getSectDonationDemands('lingxiao', '2026-07-20')[2]?.kind).not.toBe( + getSectDonationDemands('lingxiao', '2026-07-21')[2]?.kind, + ); + }); +}); diff --git a/src/server/lib/services/SectOrganizationConfig.ts b/src/server/lib/services/SectOrganizationConfig.ts new file mode 100644 index 00000000..d1b8ce8f --- /dev/null +++ b/src/server/lib/services/SectOrganizationConfig.ts @@ -0,0 +1,324 @@ +import type { + SectDiscipleRank, + SectFacilityKey, + UpgradeableSectFacilityKey, +} from '@shared/engine/sect'; +import type { SectDonationDemandData, SectTaskOffer } from '@shared/contracts/sect'; +import type { PillSpec } from '@shared/types/consumable'; +import type { Quality } from '@shared/types/constants'; + +export const SECT_TIMEZONE = 'Asia/Shanghai'; +export const SECT_DONATION_DAILY_CAP = 60; +export const SECT_FACILITY_PRIORITY: UpgradeableSectFacilityKey[] = [ + 'archive', + 'cultivation_room', + 'workshop', + 'spirit_vein', + 'herb_garden', +]; +export const SECT_PROJECT_BASE_TARGET: Record = { + 2: 250, + 3: 500, + 4: 900, + 5: 1500, +}; + +export const SECT_DAILY_TASKS: SectTaskOffer[] = [ + { + id: 'gate_sweep', + name: '清扫山门', + description: '循落叶纹路清理云阶,完成一轮山门勤务。', + kind: 'daily', + requiredRank: 'registered', + contributionReward: 25, + action: 'sweep', + available: true, + }, + { + id: 'mine_patrol', + name: '巡视矿场', + description: '前往宗门矿脉驱逐侵扰妖兽。', + kind: 'daily', + requiredRank: 'registered', + contributionReward: 30, + action: 'battle', + available: true, + }, + { + id: 'pill_delivery', + name: '丹药委托', + description: '提交一枚有效丹药,补充宗门日常储备。', + kind: 'daily', + requiredRank: 'outer', + contributionReward: 35, + action: 'submit_pill', + available: true, + }, + { + id: 'artifact_delivery', + name: '法宝委托', + description: '提交一件未装备的凡品以上法宝,交由宗门统一调度。', + kind: 'daily', + requiredRank: 'inner', + contributionReward: 45, + action: 'submit_artifact', + available: true, + }, +]; + +export const SECT_WEEKLY_TASKS = [ + { id: 'weekly_diligence', target: 5, reward: 20 }, + { id: 'weekly_tournament', target: 1, reward: 40 }, + { id: 'weekly_bounty', target: 1, reward: 60 }, +] as const; + +export type SectShopGrant = + | { + kind: 'material'; + name: string; + type: 'herb' | 'ore' | 'aux'; + quality: Quality; + element?: string; + description: string; + } + | { + kind: 'pill'; + name: string; + quality: Quality; + description: string; + spec: PillSpec; + }; + +export interface SectShopDefinition { + id: string; + requiredRank: SectDiscipleRank; + price: number; + stock: number; + rotating: boolean; + grant: SectShopGrant; +} + +function recoveryPillSpec(resource: 'hp' | 'mp', value: number): PillSpec { + return { + kind: 'pill', + family: resource === 'hp' ? 'healing' : 'mana', + operations: [ + { type: 'restore_resource', resource, mode: 'flat', value }, + { type: 'change_gauge', gauge: 'pillToxicity', delta: 3 }, + ], + consumeRules: { scene: 'out_of_battle_only', quotaCategory: 'none' }, + alchemyMeta: { + source: 'improvised', + sourceMaterials: ['宗门宝库制式丹材'], + analysisVersion: 2, + propertyVector: [ + { key: resource === 'hp' ? 'restore_hp' : 'restore_mp', weight: 1 }, + ], + sourceMaterialVectors: [], + stability: 80, + toxicityRating: 3, + appearance: 'middle', + tags: ['宗门制式'], + }, + }; +} + +export const SECT_SHOP_ITEMS: SectShopDefinition[] = [ + { + id: 'outer_qinglu', + requiredRank: 'outer', + price: 10, + stock: 8, + rotating: false, + grant: { + kind: 'material', + name: '青露草', + type: 'herb', + quality: '凡品', + element: '木', + description: '叶尖含露,药性温和的入门灵草。', + }, + }, + { + id: 'outer_recovery_pill', + requiredRank: 'outer', + price: 40, + stock: 3, + rotating: false, + grant: { + kind: 'pill', + name: '宗门回气丹', + quality: '凡品', + description: '宗门制式回气丹,可恢复少量法力。', + spec: recoveryPillSpec('mp', 40), + }, + }, + { + id: 'inner_ironwood', + requiredRank: 'inner', + price: 80, + stock: 5, + rotating: false, + grant: { + kind: 'material', + name: '百炼铁木', + type: 'aux', + quality: '玄品', + element: '木', + description: '经百次灵火淬炼的铁木,可稳定丹器结构。', + }, + }, + { + id: 'inner_healing_pill', + requiredRank: 'inner', + price: 120, + stock: 2, + rotating: true, + grant: { + kind: 'pill', + name: '玉髓回春丹', + quality: '玄品', + description: '内门储备的疗伤丹药。', + spec: recoveryPillSpec('hp', 180), + }, + }, + { + id: 'true_cloud_ore', + requiredRank: 'true', + price: 220, + stock: 2, + rotating: true, + grant: { + kind: 'material', + name: '凌霄云铁', + type: 'ore', + quality: '真品', + element: '金', + description: '凌霄峰云海灵压凝成的稀有灵铁。', + }, + }, + { + id: 'true_spirit_pill', + requiredRank: 'true', + price: 300, + stock: 1, + rotating: true, + grant: { + kind: 'pill', + name: '凌霄蕴神丹', + quality: '真品', + description: '真传弟子方可兑换的高阶回气丹。', + spec: recoveryPillSpec('mp', 260), + }, + }, +]; + +function formatDateInTimezone(date: Date): string { + return new Intl.DateTimeFormat('en-CA', { + timeZone: SECT_TIMEZONE, + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(date); +} + +export function getSectDateKey(now = new Date()): string { + return formatDateInTimezone(now); +} + +export function getSectWeekKey(now = new Date()): string { + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: SECT_TIMEZONE, + year: 'numeric', + month: '2-digit', + day: '2-digit', + weekday: 'short', + }).formatToParts(now); + const values = Object.fromEntries(parts.map((part) => [part.type, part.value])); + const local = new Date(`${values.year}-${values.month}-${values.day}T00:00:00Z`); + const weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].indexOf( + values.weekday, + ); + const daysSinceMonday = (weekday + 6) % 7; + local.setUTCDate(local.getUTCDate() - daysSinceMonday); + return local.toISOString().slice(0, 10); +} + +export function getSectBountyMode( + weekKey = getSectWeekKey(), +): 'battle' | 'material' { + const seed = [...weekKey].reduce( + (sum, char) => sum * 31 + char.charCodeAt(0), + 0, + ); + return Math.abs(seed) % 2 === 0 ? 'battle' : 'material'; +} + +export function getSectDonationDemands( + sectId: string, + dateKey = getSectDateKey(), +): SectDonationDemandData[] { + const seed = [...`${sectId}:${dateKey}`].reduce( + (sum, char) => sum + char.charCodeAt(0), + 0, + ); + const artifactDay = seed % 2 === 0; + return [ + { + id: 'spirit_stones', + name: '修缮灵石', + description: '提交 1000 灵石用于阵纹与工料周转。', + kind: 'spirit_stones', + quantity: 1000, + contribution: 10, + constructionPoints: 10, + }, + { + id: 'herb_bundle', + name: '灵草束', + description: '提交两份凡品以上灵草。', + kind: 'material', + quantity: 2, + contribution: 15, + constructionPoints: 15, + minQuality: '凡品', + }, + artifactDay + ? { + id: 'artifact_supply', + name: '备用法宝', + description: '提交一件未装备的凡品以上法宝。', + kind: 'artifact', + quantity: 1, + contribution: 40, + constructionPoints: 40, + minQuality: '凡品', + } + : { + id: 'pill_supply', + name: '丹药补给', + description: `提交一枚凡品以上${seed % 4 === 1 ? '疗伤类' : '回气类'}有效丹药。`, + kind: 'pill', + quantity: 1, + contribution: 25, + constructionPoints: 25, + minQuality: '凡品', + pillFamily: seed % 4 === 1 ? 'healing' : 'mana', + }, + ]; +} + +export function getSectStipendBase(rank: SectDiscipleRank): number { + return { + registered: 500, + outer: 1500, + inner: 4000, + true: 10000, + }[rank]; +} + +export function isUpgradeableFacility( + key: SectFacilityKey, +): key is UpgradeableSectFacilityKey { + return key !== 'formation'; +} diff --git a/src/server/lib/services/SectOrganizationService.ts b/src/server/lib/services/SectOrganizationService.ts new file mode 100644 index 00000000..7ac08dbf --- /dev/null +++ b/src/server/lib/services/SectOrganizationService.ts @@ -0,0 +1,1104 @@ +import type { DbExecutor, DbTransaction } from '@server/lib/drizzle/db'; +import { getExecutor } from '@server/lib/drizzle/db'; +import * as organizationRepository from '@server/lib/repositories/sectOrganizationRepository'; +import * as sectRepository from '@server/lib/repositories/sectRepository'; +import { addMaterialStackToInventory } from '@server/lib/services/materialInventory'; +import { + addConsumableToInventory, + getPlayerRuntimeCultivatorByIdUnsafe, + updateCultivationExp, +} from '@server/lib/services/cultivatorService'; +import type { + SectConstructionData, + SectMemberData, + SectOverviewData, + SectShopItemData, + SectTaskId, + SectTaskRecordData, + SectTasksData, +} from '@shared/contracts/sect'; +import { calculateSceneCultivationExp } from '@shared/engine/cultivation/ExpBudgetCalculator'; +import { + getSectFacilityBonus, + getEffectiveSectMethodLevelCap, + hasSectRank, + realmMeetsSectRank, + SECT_FACILITY_KEYS, + SECT_RANK_REQUIREMENTS, + type SectConstructionProjectState, + type SectDiscipleRank, + type SectFacilityState, + type UpgradeableSectFacilityKey, +} from '@shared/engine/sect'; +import { isPillSpec } from '@shared/lib/consumables'; +import { simulateBattleV5 } from '@shared/lib/battle/simulateBattleV5'; +import type { Cultivator, Material } from '@shared/types/cultivator'; +import { QUALITY_ORDER, REALM_ORDER, type Quality } from '@shared/types/constants'; +import { SectError } from './SectError'; +import { getSectFacilityBonuses } from './SectFacilityService'; +import { + getSectDateKey, + getSectBountyMode, + getSectDonationDemands, + getSectStipendBase, + getSectWeekKey, + SECT_DAILY_TASKS, + SECT_DONATION_DAILY_CAP, + SECT_FACILITY_PRIORITY, + SECT_PROJECT_BASE_TARGET, + SECT_SHOP_ITEMS, + SECT_WEEKLY_TASKS, + type SectShopDefinition, +} from './SectOrganizationConfig'; + +type Membership = NonNullable< + Awaited> +>; + +function organizationError(message: string, status = 409): never { + throw new SectError('SECT_ORGANIZATION_INVALID', message, status); +} + +async function requireMembership( + cultivatorId: string, + q: DbExecutor | DbTransaction, +): Promise { + const membership = await sectRepository.findMembership(cultivatorId, q); + if (!membership) organizationError('尚未拜入宗门'); + return membership; +} + +function mapFacilities( + rows: Awaited>, +): SectFacilityState[] { + const byKey = new Map(rows.map((row) => [row.facilityKey, row])); + return SECT_FACILITY_KEYS.map((key) => { + const row = byKey.get(key); + return { + key, + level: key === 'formation' ? 0 : (row?.level ?? 1), + updatedAt: row?.updatedAt?.toISOString(), + }; + }); +} + +function mapProject( + row: Awaited>, +): SectConstructionProjectState | null { + if (!row) return null; + return { + id: row.id, + sectId: row.sectId, + facilityKey: row.facilityKey as UpgradeableSectFacilityKey, + targetLevel: row.targetLevel, + progress: row.progress, + target: row.target, + status: row.status as 'active' | 'completed', + startedWeekKey: row.startedWeekKey, + completedAt: row.completedAt?.toISOString(), + }; +} + +function nextRank(rank: SectDiscipleRank): SectDiscipleRank | null { + return ({ registered: 'outer', outer: 'inner', inner: 'true', true: null } as const)[ + rank + ]; +} + +async function getPromotionMissing( + membership: Membership, + realm: Cultivator['realm'], + stage: Cultivator['realm_stage'], + q: DbExecutor | DbTransaction, +): Promise { + const target = nextRank(membership.discipleRank as SectDiscipleRank); + if (!target) return []; + const requirement = + SECT_RANK_REQUIREMENTS[target as Exclude]; + const missing: string[] = []; + if (!realmMeetsSectRank(realm, stage, requirement.minRealm)) + missing.push(`境界达到${requirement.minRealm}`); + if (membership.contribution < requirement.contribution) + missing.push(`当前贡献达到${requirement.contribution}`); + if (requirement.dailyCompletions) { + const completed = await organizationRepository.countCompletedDailySectTasks( + membership.id, + q, + ); + if (completed < requirement.dailyCompletions) + missing.push(`完成宗门日常 ${completed}/${requirement.dailyCompletions}`); + } + if ( + requirement.requiresTournament && + !(await organizationRepository.hasCompletedSectTask( + membership.id, + 'weekly_tournament', + q, + )) + ) + missing.push('完成一次宗门小比'); + if ( + requirement.requiresBounty && + !(await organizationRepository.hasCompletedSectTask( + membership.id, + 'weekly_bounty', + q, + )) + ) + missing.push('完成一次悬赏令'); + if ( + requirement.requiresElderTrial && + !(await organizationRepository.hasCompletedSectTask( + membership.id, + 'elder_trial', + q, + )) + ) + missing.push('通过长老试炼'); + return missing; +} + +async function ensureCurrentProject( + sectId: string, + tx: DbExecutor | DbTransaction, +): Promise { + await organizationRepository.ensureSectFacilities(sectId, tx); + const active = await organizationRepository.findActiveSectProject(sectId, tx); + if (active) return mapProject(active); + const weekKey = getSectWeekKey(); + const latest = await organizationRepository.findLatestCompletedSectProject( + sectId, + tx, + ); + if (latest?.completedAt && getSectWeekKey(latest.completedAt) === weekKey) + return null; + + const facilities = await organizationRepository.listSectFacilities(sectId, tx); + const levels = new Map(facilities.map((row) => [row.facilityKey, row.level])); + const candidate = [...SECT_FACILITY_PRIORITY] + .filter((key) => (levels.get(key) ?? 1) < 5) + .sort((a, b) => { + const levelDiff = (levels.get(a) ?? 1) - (levels.get(b) ?? 1); + return levelDiff || SECT_FACILITY_PRIORITY.indexOf(a) - SECT_FACILITY_PRIORITY.indexOf(b); + })[0]; + if (!candidate) return null; + const targetLevel = (levels.get(candidate) ?? 1) + 1; + const since = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + const activeMembers = Math.max( + 1, + Math.min( + 100, + await organizationRepository.countRecentlyActiveSectMembers( + sectId, + since, + tx, + ), + ), + ); + const created = await organizationRepository.createSectProject( + { + sectId, + facilityKey: candidate, + targetLevel, + target: (SECT_PROJECT_BASE_TARGET[targetLevel] ?? 1500) * activeMembers, + startedWeekKey: weekKey, + }, + tx, + ); + return mapProject(created); +} + +function taskTarget(taskId: SectTaskId): number { + return SECT_WEEKLY_TASKS.find((task) => task.id === taskId)?.target ?? 1; +} + +function mapTaskRecord( + row: Awaited>[number], +): SectTaskRecordData { + const payload = (row.payload ?? {}) as Record; + return { + id: row.id, + taskId: row.taskId as SectTaskId, + kind: row.kind as SectTaskRecordData['kind'], + periodKey: row.periodKey, + status: row.status as SectTaskRecordData['status'], + progress: row.progress, + target: Number(payload.target ?? taskTarget(row.taskId as SectTaskId)), + completedAt: row.completedAt?.toISOString(), + payload, + }; +} + +function cloneOpponent( + source: Cultivator, + name: string, + multiplier: number, +): Cultivator { + const opponent = structuredClone(source); + opponent.id = globalThis.crypto?.randomUUID?.() ?? `sect-${Date.now()}`; + opponent.name = name; + opponent.title = '宗门试炼残影'; + opponent.attributes = { + vitality: Math.max(1, Math.floor(source.attributes.vitality * multiplier)), + spirit: Math.max(1, Math.floor(source.attributes.spirit * multiplier)), + wisdom: Math.max(1, Math.floor(source.attributes.wisdom * multiplier)), + speed: Math.max(1, Math.floor(source.attributes.speed * multiplier)), + willpower: Math.max(1, Math.floor(source.attributes.willpower * multiplier)), + }; + return opponent; +} + +function taskReward(taskId: SectTaskId): number { + return ( + SECT_DAILY_TASKS.find((task) => task.id === taskId)?.contributionReward ?? + SECT_WEEKLY_TASKS.find((task) => task.id === taskId)?.reward ?? + 0 + ); +} + +async function grantTaskReward(args: { + userId: string; + cultivatorId: string; + membership: Membership; + taskId: SectTaskId; + referenceId: string; + daily: boolean; + tx: DbTransaction; +}) { + const contribution = taskReward(args.taskId); + if (contribution > 0) + await organizationRepository.addSectContribution( + args.membership.id, + contribution, + args.daily ? 'daily_task' : 'weekly_task', + args.referenceId, + args.tx, + ); + if (!args.daily) return; + const progress = await sectRepository.loadSectCultivatorProgress( + args.cultivatorId, + args.tx, + ); + if (!progress) return; + const exp = calculateSceneCultivationExp('daily_task', { + realm: progress.realm, + realmStage: progress.stage, + difficulty: 'easy', + }).baseExp; + await organizationRepository.addCultivatorSpiritStones( + args.cultivatorId, + (REALM_ORDER[progress.realm] + 1) * 1000, + args.tx, + ); + if (exp > 0) + await updateCultivationExp( + args.userId, + args.cultivatorId, + exp, + undefined, + args.tx, + ); +} + +async function syncWeeklyDiligence( + membership: Membership, + weekKey: string, + tx: DbTransaction, +) { + const progress = await organizationRepository.countCompletedDailySectTasksSince( + membership.id, + weekKey, + tx, + ); + const existing = await organizationRepository.findSectTaskRecord( + membership.id, + weekKey, + 'weekly_diligence', + tx, + ); + const row = await organizationRepository.upsertSectTaskProgress( + { + membershipId: membership.id, + taskId: 'weekly_diligence', + kind: 'weekly', + periodKey: weekKey, + progress: Math.min(5, progress), + target: 5, + }, + tx, + ); + if (row.status === 'completed' && existing?.status !== 'completed') + await organizationRepository.addSectContribution( + membership.id, + 20, + 'weekly_task', + row.id, + tx, + ); +} + +async function completeTaskOnce(args: { + userId: string; + cultivatorId: string; + membership: Membership; + record: NonNullable>>; + daily: boolean; + tx: DbTransaction; +}) { + const completed = await organizationRepository.completeSectTaskRecord( + args.record.id, + 1, + args.tx, + ); + if (!completed) organizationError('该宗门任务已经完成'); + await grantTaskReward({ + ...args, + taskId: completed.taskId as SectTaskId, + referenceId: completed.id, + }); + if (args.daily) + await syncWeeklyDiligence(args.membership, getSectWeekKey(), args.tx); + return mapTaskRecord(completed); +} + +function rotatingShopItems(weekKey: string): SectShopDefinition[] { + const fixed = SECT_SHOP_ITEMS.filter((item) => !item.rotating); + const rotating = SECT_SHOP_ITEMS.filter((item) => item.rotating); + if (rotating.length <= 2) return [...fixed, ...rotating]; + const seed = [...weekKey].reduce((sum, char) => sum + char.charCodeAt(0), 0); + const first = seed % rotating.length; + return [fixed, rotating[first], rotating[(first + 1) % rotating.length]].flat(); +} + +export const SectOrganizationService = { + async ensureWeeklyProject( + sectId: string, + q: DbExecutor = getExecutor(), + ) { + return ensureCurrentProject(sectId, q); + }, + + async getOverview( + cultivator: Pick, + realmMethodLevelCap: number, + q: DbExecutor = getExecutor(), + ): Promise { + const membership = await requireMembership(cultivator.id!, q); + const sect = await sectRepository.loadCultivatorSectState(cultivator.id!, q); + if (!sect) organizationError('宗门状态不存在'); + const facilities = mapFacilities( + await organizationRepository.listSectFacilities(membership.sectId, q), + ); + const project = mapProject( + await organizationRepository.findActiveSectProject(membership.sectId, q), + ); + const archiveLevel = facilities.find((item) => item.key === 'archive')?.level ?? 1; + const veinLevel = facilities.find((item) => item.key === 'spirit_vein')?.level ?? 1; + const gardenLevel = facilities.find((item) => item.key === 'herb_garden')?.level ?? 1; + const weekKey = getSectWeekKey(); + const claimed = await organizationRepository.hasClaimedSectStipend( + membership.id, + weekKey, + q, + ); + const spiritStones = Math.floor( + getSectStipendBase(sect.discipleRank ?? 'registered') * + (1 + getSectFacilityBonus('spirit_vein', veinLevel)), + ); + const rank = sect.discipleRank ?? 'registered'; + const herbQuantity = gardenLevel + (rank === 'inner' ? 3 : 0); + return { + sect, + facilities, + project, + realmMethodLevelCap, + methodLevelCap: getEffectiveSectMethodLevelCap({ + realmCap: realmMethodLevelCap, + rank: sect.discipleRank ?? 'registered', + archiveLevel, + }), + stipend: { + weekKey, + claimed, + spiritStones, + herbQuantity, + bonusRewards: [ + ...(rank === 'outer' ? ['基础回气丹 ×1'] : []), + ...(rank === 'inner' ? ['内门基础材料 ×3'] : []), + ...(rank === 'true' ? ['真品凌霄灵蕴草'] : []), + ], + }, + nextRank: nextRank(sect.discipleRank ?? 'registered'), + promotionMissing: await getPromotionMissing( + membership, + cultivator.realm, + cultivator.realm_stage, + q, + ), + }; + }, + + async promote( + cultivator: Pick, + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivator.id!, tx); + const current = membership.discipleRank as SectDiscipleRank; + const target = nextRank(current); + if (!target) organizationError('已是真传弟子'); + const missing = await getPromotionMissing( + membership, + cultivator.realm, + cultivator.realm_stage, + tx, + ); + if (missing.length) organizationError(`尚需:${missing.join('、')}`, 400); + await organizationRepository.promoteSectMembership(membership.id, target, tx); + return sectRepository.loadCultivatorSectState(cultivator.id!, tx); + }, + + async getTasks(cultivatorId: string, q: DbExecutor = getExecutor()): Promise { + const membership = await requireMembership(cultivatorId, q); + const rank = membership.discipleRank as SectDiscipleRank; + const dateKey = getSectDateKey(); + const weekKey = getSectWeekKey(); + const records = await organizationRepository.listSectTaskRecords(membership.id, q); + const daily = records.find( + (row) => row.kind === 'daily' && row.periodKey === dateKey, + ); + const weeklyDiligenceProgress = + await organizationRepository.countCompletedDailySectTasksSince( + membership.id, + weekKey, + q, + ); + const bountyMode = getSectBountyMode(weekKey); + const weekly = SECT_WEEKLY_TASKS.map((definition) => { + const row = records.find( + (record) => + record.taskId === definition.id && record.periodKey === weekKey, + ); + return row + ? mapTaskRecord(row) + : { + id: definition.id, + taskId: definition.id, + kind: 'weekly' as const, + periodKey: weekKey, + status: 'active' as const, + progress: + definition.id === 'weekly_diligence' + ? Math.min(definition.target, weeklyDiligenceProgress) + : 0, + target: definition.target, + payload: + definition.id === 'weekly_bounty' + ? { mode: bountyMode, minQuality: '玄品', quantity: 2 } + : undefined, + }; + }); + const elder = records.find((row) => row.taskId === 'elder_trial'); + return { + dateKey, + weekKey, + dailyOffers: SECT_DAILY_TASKS.map((offer) => { + const available = hasSectRank(rank, offer.requiredRank) && !daily; + return { + ...offer, + available, + unavailableReason: daily + ? '今日已经领取委托' + : available + ? undefined + : `须达${offer.requiredRank === 'outer' ? '外门' : '内门'}弟子`, + }; + }), + dailyTask: daily ? mapTaskRecord(daily) : null, + weeklyTasks: weekly, + promotionTask: elder ? mapTaskRecord(elder) : null, + }; + }, + + async acceptDaily(cultivatorId: string, taskId: SectTaskId, tx: DbTransaction) { + const membership = await requireMembership(cultivatorId, tx); + const offer = SECT_DAILY_TASKS.find((item) => item.id === taskId); + if (!offer) organizationError('未知宗门日常委托', 400); + if (!hasSectRank(membership.discipleRank as SectDiscipleRank, offer.requiredRank)) + organizationError('当前弟子职阶尚不能领取该委托', 400); + const dateKey = getSectDateKey(); + if (await organizationRepository.findDailySectTask(membership.id, dateKey, tx)) + organizationError('今日已经领取过宗门委托'); + const seed = [...`${membership.id}:${dateKey}`].reduce( + (sum, char) => sum + char.charCodeAt(0), + 0, + ); + const targetMoves = [seed % 9, (seed + 3) % 9, (seed + 6) % 9]; + const row = await organizationRepository.createSectTaskRecord( + { + membershipId: membership.id, + taskId, + kind: 'daily', + periodKey: dateKey, + payload: + taskId === 'gate_sweep' + ? { target: 1, targetMoves } + : { + target: 1, + minQuality: '凡品', + quantity: 1, + ...(taskId === 'pill_delivery' + ? { pillFamily: seed % 2 === 0 ? 'healing' : 'mana' } + : {}), + }, + }, + tx, + ); + return mapTaskRecord(row!); + }, + + async completeSweep( + userId: string, + cultivatorId: string, + moves: number[], + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivatorId, tx); + const record = await organizationRepository.findSectTaskRecord( + membership.id, + getSectDateKey(), + 'gate_sweep', + tx, + ); + if (!record) organizationError('今日未领取清扫山门委托', 400); + const targetMoves = ((record.payload as Record).targetMoves ?? []) as number[]; + if (targetMoves.some((move) => !moves.includes(move))) + organizationError('云阶尚有落叶未清理干净', 400); + return completeTaskOnce({ + userId, + cultivatorId, + membership, + record, + daily: true, + tx, + }); + }, + + async submitTaskItem( + userId: string, + cultivatorId: string, + taskId: SectTaskId, + itemId: string, + quantity: number, + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivatorId, tx); + const periodKey = taskId.startsWith('weekly_') ? getSectWeekKey() : getSectDateKey(); + let record = await organizationRepository.findSectTaskRecord( + membership.id, + periodKey, + taskId, + tx, + ); + if (!record && taskId === 'weekly_bounty') + record = await organizationRepository.createSectTaskRecord( + { + membershipId: membership.id, + taskId, + kind: 'weekly', + periodKey, + payload: { + target: 1, + mode: getSectBountyMode(periodKey), + minQuality: '玄品', + quantity: 2, + }, + }, + tx, + ); + if (!record) organizationError('未领取对应宗门委托', 400); + const payload = (record.payload ?? {}) as Record; + const requiredQuantity = Number(payload.quantity ?? 1); + if (quantity !== requiredQuantity) + organizationError(`该委托须一次提交 ${requiredQuantity} 份`, 400); + if (taskId === 'pill_delivery') { + const item = await organizationRepository.findOwnedConsumable(cultivatorId, itemId, tx); + if (!item || !isPillSpec(item.spec as never)) + organizationError('所选物品不是有效丹药', 400); + const minQuality = (payload.minQuality ?? '凡品') as Quality; + if ((QUALITY_ORDER[item.quality as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('丹药品质不足', 400); + if ( + payload.pillFamily && + (item.spec as { family?: unknown }).family !== payload.pillFamily + ) + organizationError('丹药类型不符合委托要求', 400); + if (!(await organizationRepository.consumeOwnedConsumable(item.id, quantity, tx))) + organizationError('丹药数量不足', 400); + } else if (taskId === 'artifact_delivery') { + const item = await organizationRepository.findOwnedArtifact(cultivatorId, itemId, tx); + if (!item) organizationError('未找到该法宝', 400); + if (item.isEquipped) organizationError('已装备法宝不能提交', 400); + const minQuality = (payload.minQuality ?? '凡品') as Quality; + if ((QUALITY_ORDER[item.quality as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('法宝品阶不足', 400); + if (!(await organizationRepository.consumeOwnedArtifact(item.id, tx))) + organizationError('法宝状态已变化,请重试', 400); + } else if (taskId === 'weekly_bounty') { + if (payload.mode !== 'material') + organizationError('本周悬赏为叛徒残影战,不接受材料提交', 400); + const item = await organizationRepository.findOwnedMaterial(cultivatorId, itemId, tx); + if (!item || item.quantity < quantity) + organizationError('悬赏所需材料不足', 400); + const minQuality = (payload.minQuality ?? '玄品') as Quality; + if ((QUALITY_ORDER[item.rank as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('悬赏材料品质不足', 400); + if (!(await organizationRepository.consumeOwnedMaterial(item.id, quantity, tx))) + organizationError('材料状态已变化,请重试', 400); + } else organizationError('该任务不接受物品提交', 400); + return completeTaskOnce({ + userId, + cultivatorId, + membership, + record, + daily: record.kind === 'daily', + tx, + }); + }, + + async challengeTask( + userId: string, + cultivatorId: string, + taskId: SectTaskId, + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivatorId, tx); + const isDaily = taskId === 'mine_patrol'; + const periodKey = isDaily + ? getSectDateKey() + : taskId === 'elder_trial' + ? 'permanent' + : getSectWeekKey(); + let record = await organizationRepository.findSectTaskRecord( + membership.id, + periodKey, + taskId, + tx, + ); + if (!record && ['weekly_tournament', 'weekly_bounty', 'elder_trial'].includes(taskId)) { + if (taskId === 'elder_trial') { + const rank = membership.discipleRank as SectDiscipleRank; + if (rank !== 'inner') organizationError('只有内门弟子可参加长老试炼', 400); + if (membership.contribution < 3000) organizationError('当前贡献尚未达到 3000', 400); + } + if (taskId === 'weekly_bounty' && getSectBountyMode(periodKey) !== 'battle') + organizationError('本周悬赏为稀有材料交付', 400); + record = await organizationRepository.createSectTaskRecord( + { + membershipId: membership.id, + taskId, + kind: taskId === 'elder_trial' ? 'promotion' : 'weekly', + periodKey, + payload: { target: 1, mode: 'battle' }, + }, + tx, + ); + } + if (!record) organizationError('未领取对应战斗委托', 400); + if ( + taskId === 'weekly_bounty' && + (record.payload as Record).mode !== 'battle' + ) + organizationError('本周悬赏为稀有材料交付', 400); + if (record.status === 'completed') organizationError('该试炼已经完成'); + const player = await getPlayerRuntimeCultivatorByIdUnsafe(cultivatorId, tx); + if (!player) organizationError('角色不存在', 400); + let source = player.cultivator; + let name = '矿脉侵扰妖兽'; + let multiplier = 0.75; + if (taskId === 'weekly_tournament') { + name = '同门试剑傀儡'; + multiplier = 0.95; + } else if (taskId === 'weekly_bounty') { + const mirrorId = await organizationRepository.findSectMirrorCultivatorId( + membership.sectId, + cultivatorId, + tx, + ); + const mirror = mirrorId + ? await getPlayerRuntimeCultivatorByIdUnsafe(mirrorId, tx) + : null; + source = mirror?.cultivator ?? player.cultivator; + name = '叛徒残影'; + multiplier = 1; + } else if (taskId === 'elder_trial') { + name = '传功长老剑影'; + multiplier = 1.05; + } + const opponent = cloneOpponent(source, name, multiplier); + const battle = simulateBattleV5(player.cultivator, opponent); + const won = battle.winner.id === player.cultivator.id; + let task = mapTaskRecord(record); + if (won) + task = await completeTaskOnce({ + userId, + cultivatorId, + membership, + record, + daily: isDaily, + tx, + }); + return { task, battle, won }; + }, + + async getShop(cultivatorId: string, q: DbExecutor = getExecutor()) { + const membership = await requireMembership(cultivatorId, q); + const rank = membership.discipleRank as SectDiscipleRank; + const weekKey = getSectWeekKey(); + const items: SectShopItemData[] = []; + for (const item of rotatingShopItems(weekKey)) { + if (!hasSectRank(rank, item.requiredRank)) continue; + const purchased = await organizationRepository.getPurchasedSectShopQuantity( + membership.id, + weekKey, + item.id, + q, + ); + items.push({ + id: item.id, + name: item.grant.name, + description: item.grant.description, + requiredRank: item.requiredRank, + price: item.price, + stock: item.stock, + purchased, + kind: item.grant.kind === 'pill' ? 'pill' : 'material', + rotating: item.rotating, + }); + } + return { weekKey, contribution: membership.contribution, items }; + }, + + async purchaseShopItem( + userId: string, + cultivatorId: string, + itemId: string, + quantity: number, + requestId: string | undefined, + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivatorId, tx); + if ( + requestId && + (await organizationRepository.findSectShopPurchaseByRequestId( + membership.id, + requestId, + tx, + )) + ) + return this.getShop(cultivatorId, tx); + const item = rotatingShopItems(getSectWeekKey()).find((entry) => entry.id === itemId); + if (!item) organizationError('本周宝库没有该物品', 400); + if (!hasSectRank(membership.discipleRank as SectDiscipleRank, item.requiredRank)) + organizationError('弟子职阶不足', 400); + const purchased = await organizationRepository.getPurchasedSectShopQuantity( + membership.id, + getSectWeekKey(), + item.id, + tx, + ); + if (purchased + quantity > item.stock) organizationError('本周个人库存不足', 400); + const cost = item.price * quantity; + if ( + (await organizationRepository.spendSectContribution( + membership.id, + cost, + 'sect_shop', + `${getSectWeekKey()}:${item.id}`, + tx, + )) === null + ) + organizationError('宗门贡献不足', 400); + const purchase = await organizationRepository.addSectShopPurchase( + membership.id, + getSectWeekKey(), + item.id, + quantity, + requestId, + tx, + ); + if (!purchase) organizationError('该笔兑换已经处理'); + if (item.grant.kind === 'material') { + await addMaterialStackToInventory( + cultivatorId, + { + name: item.grant.name, + type: item.grant.type, + rank: item.grant.quality, + element: item.grant.element as Material['element'], + description: item.grant.description, + details: { source: 'sect_shop' }, + quantity, + }, + tx, + ); + } else { + await addConsumableToInventory( + userId, + cultivatorId, + { + id: globalThis.crypto?.randomUUID?.() ?? `sect-pill-${Date.now()}`, + name: item.grant.name, + type: '丹药', + prompt: '宗门宝库制式丹药', + quality: item.grant.quality, + description: item.grant.description, + spec: item.grant.spec, + quantity, + }, + tx, + ); + } + return this.getShop(cultivatorId, tx); + }, + + async getConstruction( + cultivatorId: string, + q: DbExecutor = getExecutor(), + ): Promise { + const membership = await requireMembership(cultivatorId, q); + const facilities = mapFacilities( + await organizationRepository.listSectFacilities(membership.sectId, q), + ); + return { + facilities, + project: mapProject( + await organizationRepository.findActiveSectProject(membership.sectId, q), + ), + demands: getSectDonationDemands(membership.sectId), + donatedContributionToday: + await organizationRepository.sumSectDonationContributionForDate( + membership.id, + getSectDateKey(), + q, + ), + dailyContributionCap: SECT_DONATION_DAILY_CAP, + recentActivity: ( + await organizationRepository.listRecentSectDonations( + membership.sectId, + 12, + q, + ) + ).map((item) => ({ + ...item, + createdAt: item.createdAt.toISOString(), + })), + }; + }, + + async donate( + cultivatorId: string, + input: { demandId: string; itemId?: string; quantity: number; requestId?: string }, + tx: DbTransaction, + ) { + const membership = await requireMembership(cultivatorId, tx); + if ( + input.requestId && + (await organizationRepository.findSectDonationByRequestId( + membership.id, + input.requestId, + tx, + )) + ) + return this.getConstruction(cultivatorId, tx); + const project = await ensureCurrentProject(membership.sectId, tx); + if (!project) organizationError('本周工程已经完成,请待下周长老议定新工程'); + const demand = getSectDonationDemands(membership.sectId).find( + (item) => item.id === input.demandId, + ); + if (!demand) organizationError('今日没有这项宗门需求', 400); + const units = input.quantity; + const contribution = demand.contribution * units; + const current = await organizationRepository.sumSectDonationContributionForDate( + membership.id, + getSectDateKey(), + tx, + ); + if (current + contribution > SECT_DONATION_DAILY_CAP) + organizationError(`每日建设贡献上限为 ${SECT_DONATION_DAILY_CAP}`, 400); + let snapshot: Record = { kind: demand.kind, units }; + if (demand.kind === 'spirit_stones') { + const amount = demand.quantity * units; + if (!(await organizationRepository.spendCultivatorSpiritStones(cultivatorId, amount, tx))) + organizationError('灵石不足', 400); + snapshot = { ...snapshot, amount }; + } else if (demand.kind === 'material') { + if (!input.itemId) organizationError('请选择要捐献的材料', 400); + const item = await organizationRepository.findOwnedMaterial(cultivatorId, input.itemId, tx); + if (!item || item.type !== 'herb') organizationError('该需求只接收灵草', 400); + const minQuality = (demand.minQuality ?? '凡品') as Quality; + if ((QUALITY_ORDER[item.rank as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('材料品质不足', 400); + const amount = demand.quantity * units; + if (!(await organizationRepository.consumeOwnedMaterial(item.id, amount, tx))) + organizationError('材料数量不足', 400); + snapshot = { ...snapshot, itemId: item.id, name: item.name, amount }; + } else if (demand.kind === 'pill') { + if (!input.itemId) organizationError('请选择要捐献的丹药', 400); + const item = await organizationRepository.findOwnedConsumable(cultivatorId, input.itemId, tx); + if (!item || !isPillSpec(item.spec as never)) organizationError('该物品不是有效丹药', 400); + const minQuality = (demand.minQuality ?? '凡品') as Quality; + if ((QUALITY_ORDER[item.quality as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('丹药品质不足', 400); + if ( + demand.pillFamily && + (item.spec as { family?: unknown }).family !== demand.pillFamily + ) + organizationError('丹药类型不符合长老需求', 400); + if (!(await organizationRepository.consumeOwnedConsumable(item.id, units, tx))) + organizationError('丹药数量不足', 400); + snapshot = { ...snapshot, itemId: item.id, name: item.name }; + } else { + if (!input.itemId || units !== 1) organizationError('每次只能捐献一件法宝', 400); + const item = await organizationRepository.findOwnedArtifact(cultivatorId, input.itemId, tx); + if (!item) organizationError('未找到该法宝', 400); + if (item.isEquipped) organizationError('已装备法宝不能捐献', 400); + const minQuality = (demand.minQuality ?? '凡品') as Quality; + if ((QUALITY_ORDER[item.quality as Quality] ?? -1) < QUALITY_ORDER[minQuality]) + organizationError('法宝品阶不足', 400); + if (!(await organizationRepository.consumeOwnedArtifact(item.id, tx))) + organizationError('法宝状态已变化,请重试', 400); + snapshot = { ...snapshot, itemId: item.id, name: item.name, quality: item.quality }; + } + const donation = await organizationRepository.insertSectDonation( + { + membershipId: membership.id, + projectId: project.id, + dateKey: getSectDateKey(), + demandId: demand.id, + contribution, + constructionPoints: demand.constructionPoints * units, + itemSnapshot: snapshot, + requestId: input.requestId, + }, + tx, + ); + if (!donation) organizationError('该笔捐献已经处理'); + const advanced = await organizationRepository.advanceSectProject( + project.id, + demand.constructionPoints * units, + tx, + ); + if (!advanced) organizationError('工程状态已变化,请重试'); + await organizationRepository.addSectContribution( + membership.id, + contribution, + 'construction_donation', + donation.id, + tx, + ); + return this.getConstruction(cultivatorId, tx); + }, + + async claimStipend(userId: string, cultivatorId: string, tx: DbTransaction) { + const membership = await requireMembership(cultivatorId, tx); + await organizationRepository.ensureSectFacilities(membership.sectId, tx); + const facilities = mapFacilities( + await organizationRepository.listSectFacilities(membership.sectId, tx), + ); + const veinLevel = facilities.find((item) => item.key === 'spirit_vein')?.level ?? 1; + const gardenLevel = facilities.find((item) => item.key === 'herb_garden')?.level ?? 1; + const rank = membership.discipleRank as SectDiscipleRank; + const stones = Math.floor( + getSectStipendBase(rank) * + (1 + getSectFacilityBonus('spirit_vein', veinLevel)), + ); + const herbQuantity = gardenLevel + (rank === 'inner' ? 3 : 0); + const weekKey = getSectWeekKey(); + const claim = await organizationRepository.createSectStipendClaim( + { + membershipId: membership.id, + weekKey, + spiritStones: stones, + rewards: [{ kind: 'material', name: '宗门灵草', quantity: herbQuantity }], + }, + tx, + ); + if (!claim) organizationError('本周俸禄已经领取'); + await organizationRepository.addCultivatorSpiritStones(cultivatorId, stones, tx); + if (herbQuantity > 0) + await addMaterialStackToInventory( + cultivatorId, + { + name: rank === 'true' ? '凌霄灵蕴草' : '宗门灵草', + type: 'herb', + rank: rank === 'true' ? '真品' : '凡品', + element: '木', + description: '宗门药田按周分发的修行灵草。', + details: { source: 'sect_stipend' }, + quantity: herbQuantity, + }, + tx, + ); + if (rank === 'outer') { + const pill = SECT_SHOP_ITEMS.find((item) => item.id === 'outer_recovery_pill'); + if (pill?.grant.kind === 'pill') + await addConsumableToInventory( + userId, + cultivatorId, + { + id: globalThis.crypto?.randomUUID?.() ?? `stipend-${Date.now()}`, + name: pill.grant.name, + type: '丹药', + prompt: '外门弟子周俸', + quality: pill.grant.quality, + description: pill.grant.description, + spec: pill.grant.spec, + quantity: 1, + }, + tx, + ); + } + return { weekKey, spiritStones: stones, herbQuantity }; + }, + + async listMembers( + cultivatorId: string, + page: number, + pageSize: number, + q: DbExecutor = getExecutor(), + ) { + const membership = await requireMembership(cultivatorId, q); + const result = await organizationRepository.listSectMembers( + membership.sectId, + page, + pageSize, + q, + ); + return { + items: result.rows.map( + (row): SectMemberData => ({ + ...row, + joinedAt: row.joinedAt?.toISOString(), + }), + ), + page, + pageSize, + total: result.total, + }; + }, + + async getFacilityBonuses(cultivatorId: string, q: DbExecutor = getExecutor()) { + return getSectFacilityBonuses(cultivatorId, q); + }, + + async applyCraftDiscount( + cultivatorId: string, + cost: number, + q: DbExecutor = getExecutor(), + ) { + const { craftDiscount } = await this.getFacilityBonuses(cultivatorId, q); + return Math.max(0, Math.floor(cost * (1 - craftDiscount))); + }, +}; + +export type SectOrganizationServiceInstance = typeof SectOrganizationService; diff --git a/src/server/lib/services/SectService.ts b/src/server/lib/services/SectService.ts index a2dc5d1f..852e2def 100644 --- a/src/server/lib/services/SectService.ts +++ b/src/server/lib/services/SectService.ts @@ -20,30 +20,11 @@ import { productionSectRuntime } from '@shared/engine/sect/content'; import type { Cultivator } from '@shared/types/cultivator'; import { getRealmStageRank } from '@shared/config/realmProgression'; import type { SectPathDefinition } from '@shared/engine/sect'; - -export type SectErrorCode = - | 'SECT_UNKNOWN' - | 'SECT_TRIAL_REQUIRED' - | 'SECT_ALREADY_JOINED' - | 'SECT_REALM_GATE' - | 'SECT_METHOD_CAP' - | 'SECT_PATH_UNKNOWN' - | 'SECT_PATH_NOT_LEARNED' - | 'SECT_PATH_ALREADY_LEARNED' - | 'SECT_INSUFFICIENT_RESOURCES' - | 'SECT_INVALID_MERIDIAN' - | 'SECT_INVALID_LOADOUT' - | 'SECT_COMMISSION_ALREADY_CLAIMED'; - -export class SectError extends Error { - constructor( - public readonly code: SectErrorCode, - message: string, - public readonly status = 409, - ) { - super(message); - } -} +import { getEffectiveSectMethodLevelCap } from '@shared/engine/sect'; +import { ensureSectFacilities } from '@server/lib/repositories/sectOrganizationRepository'; +import { getSectFacilityBonuses } from './SectFacilityService'; +export { SectError, type SectErrorCode } from './SectError'; +import { SectError } from './SectError'; async function requireActive( repository: SectRepositoryPort, @@ -237,6 +218,8 @@ export class SectApplicationService { module.definition, tx, ); + if (this.repository === postgresSectRepository) + await ensureSectFacilities(sectId, tx); return (await this.repository.loadCultivatorSectState(cultivatorId, tx))!; } @@ -253,16 +236,25 @@ export class SectApplicationService { tx, ); const currentLevel = sect.methods[args.methodId] ?? 0; + const realmCap = module.progression.methodLevelCap( + cultivator.realm, + cultivator.stage, + ); + const organization = + this.repository === postgresSectRepository + ? await getSectFacilityBonuses(args.cultivatorId, tx) + : { retreatMultiplier: 1, craftDiscount: 0, archiveLevel: 1 }; try { assertMethodTrainingTarget({ definition, methodId: args.methodId, currentLevel, targetLevel: args.targetLevel, - levelCap: module.progression.methodLevelCap( - cultivator.realm, - cultivator.stage, - ), + levelCap: getEffectiveSectMethodLevelCap({ + realmCap, + rank: sect.discipleRank ?? 'registered', + archiveLevel: organization.archiveLevel, + }), methods: sect.methods, }); } catch (error) { diff --git a/src/server/lib/services/TaskService.ts b/src/server/lib/services/TaskService.ts index 093860fe..c6579c83 100644 --- a/src/server/lib/services/TaskService.ts +++ b/src/server/lib/services/TaskService.ts @@ -1307,13 +1307,14 @@ async function syncCultivatorTasksWithContext( const tasks = await Promise.all( records.map((record) => syncTaskRecord(context, record, options)), ); + const visibleTasks = tasks.filter((task) => task.category !== 'daily'); if (!options.hideCompletedBreakthrough) { - return tasks; + return visibleTasks; } const currentMajorDefinition = getCurrentMajorDefinition(context); - return tasks.filter( + return visibleTasks.filter( (task) => !( task.category === 'breakthrough_major' && @@ -1518,11 +1519,7 @@ export const TaskService = { for (const originalRecord of records) { const definition = getTaskDefinition(originalRecord.definitionId); - if ( - !definition || - (!isDailyTaskDefinition(definition) && - !isTutorialTaskDefinition(definition)) - ) { + if (!definition || !isTutorialTaskDefinition(definition)) { continue; } diff --git a/src/server/lib/services/alchemyServiceV2.ts b/src/server/lib/services/alchemyServiceV2.ts index a9f85107..dff389b8 100644 --- a/src/server/lib/services/alchemyServiceV2.ts +++ b/src/server/lib/services/alchemyServiceV2.ts @@ -61,6 +61,7 @@ import { getPlayerRuntimeCultivatorByIdUnsafe, } from './cultivatorService'; import { getMysteryMaterialBlockingReason } from './materialMysteryGuard'; +import { SectOrganizationService } from './SectOrganizationService'; export { synthesizeAlchemyFromPlan as synthesizeAlchemy } from './AlchemyRecipeRules'; export { AlchemyServiceError } from './AlchemyServiceError'; @@ -345,12 +346,16 @@ export async function previewAlchemySelection( const highestMaterialRank = pickHighestRank(rows); const fateContext = evaluateFateContext(fates); - const spiritStones = highestMaterialRank + const baseSpiritStones = highestMaterialRank ? scaleFateAdjustedValue( calculateCraftCost(highestMaterialRank, 'spiritStone'), getAlchemySpiritStoneMultiplier(fateContext), ) : 0; + const spiritStones = await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseSpiritStones, + ); const validation = buildSelectionValidation(rows, materialQuantities); const preparedMaterials = validation.valid @@ -411,10 +416,15 @@ export function createAlchemyService( const fateContext = evaluateFateContext( fullCultivator?.cultivator.pre_heaven_fates ?? [], ); - const cost = scaleFateAdjustedValue( + const baseCost = scaleFateAdjustedValue( calculateCraftCost(highestMaterialRank, 'spiritStone'), getAlchemySpiritStoneMultiplier(fateContext), ); + const cost = await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseCost, + options.tx ?? getExecutor(), + ); if ((cultivator.spirit_stones ?? 0) < cost) { throw new AlchemyServiceError(`灵石不足,需要 ${cost} 枚`); diff --git a/src/server/lib/services/creationServiceV2.ts b/src/server/lib/services/creationServiceV2.ts index 29070d1b..9a521d8d 100644 --- a/src/server/lib/services/creationServiceV2.ts +++ b/src/server/lib/services/creationServiceV2.ts @@ -56,6 +56,7 @@ import type { Material, PreHeavenFate } from '@shared/types/cultivator'; import { eq, inArray, sql } from 'drizzle-orm'; import { getPlayerRuntimeCultivatorByIdUnsafe } from './cultivatorService'; import { getMysteryMaterialBlockingReason } from './materialMysteryGuard'; +import { SectOrganizationService } from './SectOrganizationService'; /** * processCreation 时的玩家侧可选入参。 @@ -509,13 +510,21 @@ export async function processCreation( ); const resourceType = productType === 'artifact' ? 'spiritStone' : 'comprehension'; - const costAmount = calculateFateAdjustedCraftCost( + const baseCostAmount = calculateFateAdjustedCraftCost( highestMaterialRank, resourceType, resourceType === 'spiritStone' ? getRefineSpiritStoneMultiplier(fateContext) : fateContext.enlightenmentInsightMultiplier, ); + const costAmount = + resourceType === 'spiritStone' + ? await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseCostAmount, + q, + ) + : baseCostAmount; // 校验资源是否充足 if (resourceType === 'spiritStone') { @@ -875,23 +884,30 @@ function extractAffixSummary( } /** 造物消耗预估(供 GET /api/craft 使用) */ -export function estimateCost( +export async function estimateCost( selectedMaterials: Array<{ rank: Quality }>, craftType: string, fates: PreHeavenFate[] = [], -): { spiritStones?: number; comprehension?: number } { + cultivatorId?: string, +): Promise<{ spiritStones?: number; comprehension?: number }> { const productType = getCreationProductTypeFromCraftType(craftType); if (!productType) return {}; const highestMaterialRank = calculateHighestMaterialRank(selectedMaterials); const fateContext = evaluateFateContext(fates); if (productType === 'artifact') { + const baseCost = calculateFateAdjustedCraftCost( + highestMaterialRank, + 'spiritStone', + getRefineSpiritStoneMultiplier(fateContext), + ); return { - spiritStones: calculateFateAdjustedCraftCost( - highestMaterialRank, - 'spiritStone', - getRefineSpiritStoneMultiplier(fateContext), - ), + spiritStones: cultivatorId + ? await SectOrganizationService.applyCraftDiscount( + cultivatorId, + baseCost, + ) + : baseCost, }; } diff --git a/src/server/lib/services/taskDefinitions.ts b/src/server/lib/services/taskDefinitions.ts index e0da5c39..b784024c 100644 --- a/src/server/lib/services/taskDefinitions.ts +++ b/src/server/lib/services/taskDefinitions.ts @@ -1074,7 +1074,7 @@ export function getBreakthroughTaskDefinitionByTransition( } export function getDailyTaskDefinitions() { - return dailyDefinitions; + return [] as DailyTaskDefinition[]; } export function getTutorialTaskDefinitions() { diff --git a/src/server/routes/api/craft.router.ts b/src/server/routes/api/craft.router.ts index eb5e33fd..9f681025 100644 --- a/src/server/routes/api/craft.router.ts +++ b/src/server/routes/api/craft.router.ts @@ -445,14 +445,20 @@ router.get('/', requireActiveCultivator(), async (c) => { materialIds, craftType, ); - cost = estimateCost( + cost = await estimateCost( preview.materials as Array<{ rank: Quality }>, craftType, fateList, + cultivator.id, ); validation = preview.validation; } else { - cost = estimateCost([{ rank: '凡品' }], craftType, fateList); + cost = await estimateCost( + [{ rank: '凡品' }], + craftType, + fateList, + cultivator.id, + ); } if (cost.spiritStones !== undefined) { diff --git a/src/server/routes/api/cultivator.router.test.ts b/src/server/routes/api/cultivator.router.test.ts index f8728e19..85605782 100644 --- a/src/server/routes/api/cultivator.router.test.ts +++ b/src/server/routes/api/cultivator.router.test.ts @@ -25,6 +25,16 @@ vi.mock('@server/lib/drizzle/db', () => ({ getExecutor: vi.fn(), })); +vi.mock('@server/lib/services/SectOrganizationService', () => ({ + SectOrganizationService: { + getFacilityBonuses: vi.fn(async () => ({ + retreatMultiplier: 1, + craftDiscount: 0, + archiveLevel: 1, + })), + }, +})); + vi.mock('@server/lib/repositories/playerStateRepository', async (importOriginal) => { const actual = await importOriginal< diff --git a/src/server/routes/api/cultivator.router.ts b/src/server/routes/api/cultivator.router.ts index be083d6b..98c66f92 100644 --- a/src/server/routes/api/cultivator.router.ts +++ b/src/server/routes/api/cultivator.router.ts @@ -120,6 +120,7 @@ import { attemptBreakthrough, performCultivation, } from '@shared/engine/cultivation/CultivationEngine'; +import { SectOrganizationService } from '@server/lib/services/SectOrganizationService'; import { MaterialGenerator } from '@shared/engine/material/creation/MaterialGenerator'; import type { GeneratedMaterial } from '@shared/engine/material/creation/types'; import { resourceEngine } from '@shared/engine/resource/ResourceEngine'; @@ -1705,7 +1706,12 @@ router.post('/retreat', requireActiveCultivator(), async (c) => { } const cultivateQiActionInstanceId = randomUUID(); - const result = performCultivation(cultivator, years); + const sectBonuses = await SectOrganizationService.getFacilityBonuses( + cultivatorId, + ); + const result = performCultivation(cultivator, years, Math.random, { + retreatExpMultiplier: sectBonuses.retreatMultiplier, + }); let streamResult: RetreatResultData = { summary: result.summary, diff --git a/src/server/routes/api/dungeon.router.ts b/src/server/routes/api/dungeon.router.ts index bde5e93b..bb36ba86 100644 --- a/src/server/routes/api/dungeon.router.ts +++ b/src/server/routes/api/dungeon.router.ts @@ -24,7 +24,6 @@ import { toPlayerStateMutationResponse, type StateChangeDescriptor, } from '@server/lib/services/PlayerStateMutationService'; -import { SectCommissionService } from '@server/lib/services/SectCommissionService'; import { hasCultivatorRecoveryPill } from '@server/lib/repositories/cultivatorRepository'; import { buildCultivatorRuntime, @@ -234,9 +233,6 @@ async function commitDungeonResponse(args: { if (persist) { await persist(tx); } - const commissionCompleted = args.includeTasks - ? await SectCommissionService.recordEvent(args.cultivatorId, 'dungeon', tx) - : false; const after = await readDungeonPlayerState(tx, args.cultivatorId); const changes = buildDungeonStateChanges({ before, @@ -244,7 +240,6 @@ async function commitDungeonResponse(args: { result: responseResult, includeTasks: args.includeTasks, }); - if (commissionCompleted) changes.push({ domain: 'sect', eventType: 'sect.commission_completed' }); return { result: responseResult, changes, diff --git a/src/server/routes/api/rankings.router.ts b/src/server/routes/api/rankings.router.ts index 76e185f3..9efb3de1 100644 --- a/src/server/routes/api/rankings.router.ts +++ b/src/server/routes/api/rankings.router.ts @@ -51,7 +51,6 @@ import type { } from '@shared/types/rankings'; import { and, desc, eq, inArray, isNotNull } from 'drizzle-orm'; import { Hono } from 'hono'; -import { SectCommissionService } from '@server/lib/services/SectCommissionService'; import { z } from 'zod'; const ChallengeSchema = z.object({ @@ -699,10 +698,6 @@ challengeRouter.post( 'ranking_challenge_battled', { tx }, ); - const commissionCompleted = typeof (tx as { select?: unknown }).select === 'function' - ? await SectCommissionService.recordEvent(cultivatorId, 'ranking', tx) - : false; - return { result: { type: 'battle_result' as const, @@ -723,7 +718,6 @@ challengeRouter.post( eventType: 'tasks.ranking.challenge_battled', invalidates: ['tasks'], }, - ...(commissionCompleted ? [{ domain: 'sect' as const, eventType: 'sect.commission_completed' }] : []), ], }; }, diff --git a/src/server/routes/api/sects.router.test.ts b/src/server/routes/api/sects.router.test.ts index d5fa90ed..93cf6c69 100644 --- a/src/server/routes/api/sects.router.test.ts +++ b/src/server/routes/api/sects.router.test.ts @@ -36,8 +36,43 @@ vi.mock('@server/lib/hono/middleware', () => { await next(); }, getValidatedJson: (context: any) => context.get('validatedJson'), + validateQuery: () => async (_context: any, next: () => Promise) => { + await next(); + }, + getValidatedQuery: () => ({ page: 1, pageSize: 20 }), }; }); +vi.mock('@server/lib/services/SectOrganizationService', () => ({ + SectOrganizationService: { + getOverview: vi.fn(async () => ({ + facilities: [], + project: null, + methodLevelCap: 20, + realmMethodLevelCap: 25, + stipend: { + weekKey: '2026-07-13', + claimed: false, + spiritStones: 500, + herbQuantity: 1, + bonusRewards: [], + }, + nextRank: 'outer', + promotionMissing: [], + })), + getTasks: vi.fn(), + getShop: vi.fn(), + getConstruction: vi.fn(), + listMembers: vi.fn(), + acceptDaily: vi.fn(), + completeSweep: vi.fn(), + submitTaskItem: vi.fn(), + challengeTask: vi.fn(), + promote: vi.fn(), + purchaseShopItem: vi.fn(), + donate: vi.fn(), + claimStipend: vi.fn(), + }, +})); vi.mock('@server/lib/services/SectService', () => { class SectError extends Error { constructor( @@ -127,7 +162,7 @@ const activeSect = { describe('sects router', () => { beforeEach(() => vi.clearAllMocks()); - it('returns current race, membership, cap and commission', async () => { + it('returns current race, membership and organization overview', async () => { getStateMock.mockResolvedValue(activeSect); getTodayMock.mockResolvedValue({ dateKey: '2026-07-13' }); const response = await new Hono() @@ -138,9 +173,9 @@ describe('sects router', () => { success: true, data: { playerRace: 'human', - methodLevelCap: 25, + methodLevelCap: 20, sect: { status: 'active' }, - commission: { dateKey: '2026-07-13' }, + overview: { nextRank: 'outer' }, }, }); }); diff --git a/src/server/routes/api/sects.router.ts b/src/server/routes/api/sects.router.ts index 8385c85a..227f4435 100644 --- a/src/server/routes/api/sects.router.ts +++ b/src/server/routes/api/sects.router.ts @@ -2,15 +2,17 @@ import type { DbTransaction } from '@server/lib/drizzle/db'; import { getExecutor } from '@server/lib/drizzle/db'; import { getValidatedJson, + getValidatedQuery, requireActiveCultivator, validateJson, + validateQuery, } from '@server/lib/hono/middleware'; import type { AppEnv } from '@server/lib/hono/types'; import { commitPlayerStateMutation, toPlayerStateMutationResponse, } from '@server/lib/services/PlayerStateMutationService'; -import { SectCommissionService } from '@server/lib/services/SectCommissionService'; +import { SectOrganizationService } from '@server/lib/services/SectOrganizationService'; import { SectError, SectService, @@ -19,9 +21,16 @@ import { import { getPlayerRuntimeCultivatorById } from '@server/lib/services/cultivatorService'; import { SectAbilityLoadoutRequestSchema, + SectDailyAcceptRequestSchema, + SectDonationRequestSchema, + SectMembersQuerySchema, SectMeridianLoadoutRequestSchema, SectMethodTrainRequestSchema, + SectShopPurchaseRequestSchema, + SectSweepCompleteRequestSchema, + SectTaskSubmitRequestSchema, SectTacticRequestSchema, + type SectTaskId, } from '@shared/contracts/sect'; import { listUnlockedAbilityIds, @@ -98,10 +107,25 @@ export function createSectsRouter( const definition = sect ? runtime.registry.require(sect.sectId).definition : null; - const commission = await SectCommissionService.getToday( - cultivator.id, - getExecutor(), - ); + const realmMethodLevelCap = sect + ? runtime + .progressionFor(sect.sectId) + .methodLevelCap( + cultivator.realm as RealmType, + cultivator.realm_stage as RealmStage, + ) + : 0; + const overview = sect + ? await SectOrganizationService.getOverview( + { + id: cultivator.id, + realm: cultivator.realm as RealmType, + realm_stage: cultivator.realm_stage as RealmStage, + }, + realmMethodLevelCap, + getExecutor(), + ) + : null; return c.json({ success: true, data: { @@ -109,21 +133,351 @@ export function createSectsRouter( raceNarrative: cultivator.raceNarrative, definition, sect: sect ?? null, - methodLevelCap: sect - ? runtime - .progressionFor(sect.sectId) - .methodLevelCap( - cultivator.realm as RealmType, - cultivator.realm_stage as RealmStage, - ) - : 0, + methodLevelCap: overview?.methodLevelCap ?? 0, knownAbilityIds: sect && definition ? listUnlockedAbilityIds(definition, sect) : [], - commission, + overview, }, }); }); + router.get('/current/overview', requireActiveCultivator(), async (c) => { + const cultivator = c.get('cultivator'); + if (!cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + const sect = await sectService.getState(cultivator.id, getExecutor()); + if (!sect) throw new SectError('SECT_TRIAL_REQUIRED', '尚未拜入宗门'); + const realmCap = runtime + .progressionFor(sect.sectId) + .methodLevelCap( + cultivator.realm as RealmType, + cultivator.realm_stage as RealmStage, + ); + return c.json({ + success: true, + data: await SectOrganizationService.getOverview( + { + id: cultivator.id, + realm: cultivator.realm as RealmType, + realm_stage: cultivator.realm_stage as RealmStage, + }, + realmCap, + getExecutor(), + ), + }); + } catch (error) { + return failure(c, error); + } + }); + + router.get('/current/tasks', requireActiveCultivator(), async (c) => { + const cultivator = c.get('cultivator'); + if (!cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + return c.json({ + success: true, + data: await SectOrganizationService.getTasks(cultivator.id), + }); + } catch (error) { + return failure(c, error); + } + }); + + router.get('/current/shop', requireActiveCultivator(), async (c) => { + const cultivator = c.get('cultivator'); + if (!cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + return c.json({ + success: true, + data: await SectOrganizationService.getShop(cultivator.id), + }); + } catch (error) { + return failure(c, error); + } + }); + + router.get('/current/construction', requireActiveCultivator(), async (c) => { + const cultivator = c.get('cultivator'); + if (!cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + return c.json({ + success: true, + data: await SectOrganizationService.getConstruction(cultivator.id), + }); + } catch (error) { + return failure(c, error); + } + }); + + router.get( + '/current/members', + requireActiveCultivator(), + validateQuery(SectMembersQuerySchema), + async (c) => { + const cultivator = c.get('cultivator'); + if (!cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + const query = getValidatedQuery<{ page: number; pageSize: number }>(c); + return c.json({ + success: true, + data: await SectOrganizationService.listMembers( + cultivator.id, + query.page, + query.pageSize, + ), + }); + } catch (error) { + return failure(c, error); + } + }, + ); + + const organizationMutation = async ( + c: Context, + source: string, + run: (args: { + userId: string; + cultivatorId: string; + tx: DbTransaction; + }) => Promise, + changes: Array<{ + domain: 'sect' | 'tasks' | 'loadout' | 'currency' | 'progress'; + eventType: string; + invalidates?: Array<'sect' | 'tasks' | 'loadout' | 'currency' | 'progress'>; + }>, + ) => { + const user = c.get('user'); + const cultivator = c.get('cultivator'); + if (!user || !cultivator?.id) + return c.json({ success: false, error: '当前没有活跃角色' }, 404); + try { + const committed = await commitPlayerStateMutation({ + userId: user.id, + cultivatorId: cultivator.id, + source, + requestId: c.req.header('x-request-id'), + run: async (tx) => ({ + result: await run({ + userId: user.id, + cultivatorId: cultivator.id!, + tx, + }), + changes, + }), + }); + return c.json(toPlayerStateMutationResponse(committed)); + } catch (error) { + return failure(c, error); + } + }; + + router.post( + '/current/tasks/daily/accept', + requireActiveCultivator(), + validateJson(SectDailyAcceptRequestSchema), + async (c) => { + const body = getValidatedJson<{ taskId: SectTaskId }>(c); + return organizationMutation( + c, + 'sect_daily_accept', + ({ cultivatorId, tx }) => + SectOrganizationService.acceptDaily(cultivatorId, body.taskId, tx), + [{ domain: 'tasks', eventType: 'sect.task_accepted' }], + ); + }, + ); + + router.post( + '/current/tasks/gate_sweep/complete', + requireActiveCultivator(), + validateJson(SectSweepCompleteRequestSchema), + async (c) => { + const body = getValidatedJson<{ moves: number[] }>(c); + return organizationMutation( + c, + 'sect_gate_sweep', + ({ userId, cultivatorId, tx }) => + SectOrganizationService.completeSweep( + userId, + cultivatorId, + body.moves, + tx, + ), + [ + { + domain: 'tasks', + eventType: 'sect.daily_completed', + invalidates: ['sect', 'currency', 'progress'], + }, + { domain: 'sect', eventType: 'sect.contribution_earned' }, + { domain: 'currency', eventType: 'sect.daily_stones_earned' }, + { domain: 'progress', eventType: 'sect.daily_cultivation_earned' }, + ], + ); + }, + ); + + router.post( + '/current/tasks/:taskId/submit', + requireActiveCultivator(), + validateJson(SectTaskSubmitRequestSchema), + async (c) => { + const body = getValidatedJson<{ itemId: string; quantity: number }>(c); + return organizationMutation( + c, + 'sect_task_submit', + ({ userId, cultivatorId, tx }) => + SectOrganizationService.submitTaskItem( + userId, + cultivatorId, + c.req.param('taskId') as SectTaskId, + body.itemId, + body.quantity, + tx, + ), + [ + { + domain: 'tasks', + eventType: 'sect.task_submitted', + invalidates: ['sect', 'loadout', 'currency', 'progress'], + }, + { domain: 'sect', eventType: 'sect.contribution_earned' }, + { domain: 'loadout', eventType: 'sect.task_item_consumed' }, + ], + ); + }, + ); + + router.post( + '/current/tasks/:taskId/challenge', + requireActiveCultivator(), + async (c) => + organizationMutation( + c, + 'sect_task_challenge', + ({ userId, cultivatorId, tx }) => + SectOrganizationService.challengeTask( + userId, + cultivatorId, + c.req.param('taskId') as SectTaskId, + tx, + ), + [ + { + domain: 'tasks', + eventType: 'sect.task_challenged', + invalidates: ['sect', 'currency', 'progress'], + }, + { domain: 'sect', eventType: 'sect.contribution_maybe_earned' }, + ], + ), + ); + + router.post('/current/promotion', requireActiveCultivator(), async (c) => + organizationMutation( + c, + 'sect_promotion', + ({ cultivatorId, tx }) => { + const cultivator = c.get('cultivator')!; + return SectOrganizationService.promote( + { + id: cultivatorId, + realm: cultivator.realm as RealmType, + realm_stage: cultivator.realm_stage as RealmStage, + }, + tx, + ); + }, + [{ domain: 'sect', eventType: 'sect.promoted' }], + ), + ); + + router.post( + '/current/shop/purchase', + requireActiveCultivator(), + validateJson(SectShopPurchaseRequestSchema), + async (c) => { + const body = getValidatedJson<{ + itemId: string; + quantity: number; + requestId?: string; + }>(c); + return organizationMutation( + c, + 'sect_shop_purchase', + ({ userId, cultivatorId, tx }) => + SectOrganizationService.purchaseShopItem( + userId, + cultivatorId, + body.itemId, + body.quantity, + body.requestId, + tx, + ), + [ + { + domain: 'sect', + eventType: 'sect.shop_purchased', + invalidates: ['loadout'], + }, + { domain: 'loadout', eventType: 'sect.shop_item_granted' }, + ], + ); + }, + ); + + router.post( + '/current/construction/donate', + requireActiveCultivator(), + validateJson(SectDonationRequestSchema), + async (c) => { + const body = getValidatedJson<{ + demandId: string; + itemId?: string; + quantity: number; + requestId?: string; + }>(c); + return organizationMutation( + c, + 'sect_construction_donate', + ({ cultivatorId, tx }) => + SectOrganizationService.donate(cultivatorId, body, tx), + [ + { + domain: 'sect', + eventType: 'sect.construction_donated', + invalidates: ['loadout', 'currency'], + }, + { domain: 'loadout', eventType: 'sect.donation_item_consumed' }, + { domain: 'currency', eventType: 'sect.donation_currency_changed' }, + ], + ); + }, + ); + + router.post('/current/stipend/claim', requireActiveCultivator(), async (c) => + organizationMutation( + c, + 'sect_stipend_claim', + ({ userId, cultivatorId, tx }) => + SectOrganizationService.claimStipend(userId, cultivatorId, tx), + [ + { + domain: 'sect', + eventType: 'sect.stipend_claimed', + invalidates: ['loadout', 'currency'], + }, + { domain: 'loadout', eventType: 'sect.stipend_items_granted' }, + { domain: 'currency', eventType: 'sect.stipend_stones_granted' }, + ], + ), + ); + router.get('/:sectId', requireActiveCultivator(), async (c) => { const cultivator = c.get('cultivator'); if (!cultivator?.id) @@ -407,96 +761,6 @@ export function createSectsRouter( }, ); - router.post( - '/current/commissions/spar', - requireActiveCultivator(), - async (c) => { - const user = c.get('user'); - const cultivator = c.get('cultivator'); - if (!user || !cultivator?.id) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const runtime = await getPlayerRuntimeCultivatorById( - user.id, - cultivator.id, - getExecutor(), - ); - if (!runtime) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - const battle = simulateBattleV5(runtime, { - ...runtime, - id: `${cultivator.id}-spar`, - name: '宗门演武傀儡', - }); - const committed = await commitPlayerStateMutation({ - userId: user.id, - cultivatorId: cultivator.id, - source: 'sect_commission_spar', - allowEmpty: true, - run: async (tx) => { - const completed = await SectCommissionService.recordEvent( - cultivator.id!, - 'spar', - tx, - ); - return { - result: { completed, battle }, - changes: completed - ? [ - { - domain: 'sect' as const, - eventType: 'sect.commission_completed', - }, - ] - : [], - }; - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return failure(c, error); - } - }, - ); - - router.post( - '/current/commissions/claim', - requireActiveCultivator(), - async (c) => { - const user = c.get('user'); - const cultivator = c.get('cultivator'); - if (!user || !cultivator?.id) - return c.json({ success: false, error: '当前没有活跃角色' }, 404); - try { - const committed = await commitPlayerStateMutation({ - userId: user.id, - cultivatorId: cultivator.id, - source: 'sect_commission_claim', - run: async (tx) => { - const result = await SectCommissionService.claim( - cultivator.id!, - cultivator.realm as RealmType, - tx, - ); - return { - result, - changes: [ - { - domain: 'sect' as const, - eventType: 'sect.commission_claimed', - patch: { sect: result.sect }, - }, - ], - }; - }, - }); - return c.json(toPlayerStateMutationResponse(committed)); - } catch (error) { - return failure(c, error); - } - }, - ); - return router; } diff --git a/src/server/routes/internal/cron.router.test.ts b/src/server/routes/internal/cron.router.test.ts index fac2c114..39f3e3ea 100644 --- a/src/server/routes/internal/cron.router.test.ts +++ b/src/server/routes/internal/cron.router.test.ts @@ -8,6 +8,7 @@ const { runMaterialLibraryDailyGenerationJobMock, runPlayerStateEventsCleanupJobMock, runRankRewardsJobMock, + runSectConstructionWeeklyJobMock, runTowerEnemySetRefreshJobMock, } = vi.hoisted(() => ({ runAuctionExpireJobMock: vi.fn(), @@ -17,6 +18,7 @@ const { runMaterialLibraryDailyGenerationJobMock: vi.fn(), runPlayerStateEventsCleanupJobMock: vi.fn(), runRankRewardsJobMock: vi.fn(), + runSectConstructionWeeklyJobMock: vi.fn(), runTowerEnemySetRefreshJobMock: vi.fn(), })); @@ -29,6 +31,7 @@ vi.mock('@server/lib/jobs/internalCron', () => ({ runMaterialLibraryDailyGenerationJobMock, runPlayerStateEventsCleanupJob: runPlayerStateEventsCleanupJobMock, runRankRewardsJob: runRankRewardsJobMock, + runSectConstructionWeeklyJob: runSectConstructionWeeklyJobMock, runTowerEnemySetRefreshJob: runTowerEnemySetRefreshJobMock, })); @@ -55,6 +58,10 @@ describe('cron router', () => { ['/internal/cron/bet-battle-expire', runBetBattleExpireJobMock], ['/internal/cron/market-refresh', runMarketRefreshCronJobMock], ['/internal/cron/rank-rewards', runRankRewardsJobMock], + [ + '/internal/cron/sect-construction-weekly', + runSectConstructionWeeklyJobMock, + ], ['/internal/cron/tower-enemy-sets', runTowerEnemySetRefreshJobMock], [ '/internal/cron/player-state-events-cleanup', @@ -102,6 +109,11 @@ describe('cron router', () => { settlementDate: '2026-05-15', }, ], + [ + '/internal/cron/sect-construction-weekly', + runSectConstructionWeeklyJobMock, + { success: true, processed: 1, skipped: false }, + ], [ '/internal/cron/tower-enemy-sets', runTowerEnemySetRefreshJobMock, diff --git a/src/server/routes/internal/cron.router.ts b/src/server/routes/internal/cron.router.ts index 203a082c..17caffa8 100644 --- a/src/server/routes/internal/cron.router.ts +++ b/src/server/routes/internal/cron.router.ts @@ -6,6 +6,7 @@ import { runMaterialLibraryDailyGenerationJob, runPlayerStateEventsCleanupJob, runRankRewardsJob, + runSectConstructionWeeklyJob, runTowerEnemySetRefreshJob, } from '@server/lib/jobs/internalCron'; import type { AppEnv } from '@server/lib/hono/types'; @@ -72,6 +73,10 @@ router.get('/rank-rewards', (c) => handleCronRequest(c.req.raw, runRankRewardsJob), ); +router.get('/sect-construction-weekly', (c) => + handleCronRequest(c.req.raw, runSectConstructionWeeklyJob), +); + router.get('/market-refresh', (c) => handleCronRequest(c.req.raw, runMarketRefreshCronJob), ); diff --git a/src/shared/contracts/sect.ts b/src/shared/contracts/sect.ts index d4055cf4..71030058 100644 --- a/src/shared/contracts/sect.ts +++ b/src/shared/contracts/sect.ts @@ -1,4 +1,10 @@ -import type { CultivatorSectState, SectDefinition } from '@shared/engine/sect'; +import type { + CultivatorSectState, + SectConstructionProjectState, + SectDefinition, + SectDiscipleRank, + SectFacilityState, +} from '@shared/engine/sect'; import type { BattleRecord } from '@shared/types/battle'; import { z } from 'zod'; import type { PlayerStateMutationResponse } from './player'; @@ -16,6 +22,154 @@ export const SectAbilityLoadoutRequestSchema = z.object({ export const SectTacticRequestSchema = z.object({ tacticId: z.string().min(1).max(32), }); +export const SectDailyAcceptRequestSchema = z.object({ + taskId: z.enum(['gate_sweep', 'mine_patrol', 'pill_delivery', 'artifact_delivery']), +}); +export const SectTaskSubmitRequestSchema = z.object({ + itemId: z.string().uuid(), + quantity: z.number().int().positive().max(99).default(1), +}); +export const SectSweepCompleteRequestSchema = z.object({ + moves: z.array(z.number().int().min(0).max(8)).min(3).max(12), +}); +export const SectShopPurchaseRequestSchema = z.object({ + itemId: z.string().min(1).max(64), + quantity: z.number().int().positive().max(10).default(1), + requestId: z.string().min(8).max(128).optional(), +}); +export const SectDonationRequestSchema = z.object({ + demandId: z.string().min(1).max(64), + itemId: z.string().uuid().optional(), + quantity: z.number().int().positive().max(9999).default(1), + requestId: z.string().min(8).max(128).optional(), +}); +export const SectMembersQuerySchema = z.object({ + page: z.coerce.number().int().positive().default(1), + pageSize: z.coerce.number().int().min(1).max(50).default(20), +}); + +export type SectTaskId = + | 'gate_sweep' + | 'mine_patrol' + | 'pill_delivery' + | 'artifact_delivery' + | 'weekly_diligence' + | 'weekly_tournament' + | 'weekly_bounty' + | 'elder_trial'; + +export interface SectTaskOffer { + id: SectTaskId; + name: string; + description: string; + kind: 'daily' | 'weekly' | 'promotion'; + requiredRank: SectDiscipleRank; + contributionReward: number; + action: 'sweep' | 'battle' | 'submit_pill' | 'submit_artifact' | 'progress'; + available: boolean; + unavailableReason?: string; +} + +export interface SectTaskRecordData { + id: string; + taskId: SectTaskId; + kind: 'daily' | 'weekly' | 'promotion'; + periodKey: string; + status: 'active' | 'completed'; + progress: number; + target: number; + completedAt?: string; + payload?: Record; +} + +export interface SectTasksData { + dateKey: string; + weekKey: string; + dailyOffers: SectTaskOffer[]; + dailyTask: SectTaskRecordData | null; + weeklyTasks: SectTaskRecordData[]; + promotionTask: SectTaskRecordData | null; +} + +export interface SectShopItemData { + id: string; + name: string; + description: string; + requiredRank: SectDiscipleRank; + price: number; + stock: number; + purchased: number; + kind: 'material' | 'pill'; + rotating: boolean; +} + +export interface SectShopData { + weekKey: string; + contribution: number; + items: SectShopItemData[]; +} + +export interface SectDonationDemandData { + id: string; + name: string; + description: string; + kind: 'spirit_stones' | 'material' | 'pill' | 'artifact'; + quantity: number; + contribution: number; + constructionPoints: number; + minQuality?: string; + pillFamily?: string; +} + +export interface SectOverviewData { + sect: CultivatorSectState; + facilities: SectFacilityState[]; + project: SectConstructionProjectState | null; + methodLevelCap: number; + realmMethodLevelCap: number; + stipend: { + weekKey: string; + claimed: boolean; + spiritStones: number; + herbQuantity: number; + bonusRewards: string[]; + }; + nextRank: SectDiscipleRank | null; + promotionMissing: string[]; +} + +export interface SectConstructionData { + facilities: SectFacilityState[]; + project: SectConstructionProjectState | null; + demands: SectDonationDemandData[]; + donatedContributionToday: number; + dailyContributionCap: number; + recentActivity: Array<{ + id: string; + memberName: string; + demandId: string; + contribution: number; + constructionPoints: number; + createdAt: string; + }>; +} + +export interface SectMemberData { + cultivatorId: string; + name: string; + realm: string; + realmStage: string; + discipleRank: SectDiscipleRank; + office: CultivatorSectState['office']; + joinedAt?: string; +} + +export interface SectMembersData { + items: SectMemberData[]; + page: number; + pageSize: number; + total: number; +} export type SectCatalogEntry = { definition: SectDefinition; @@ -37,12 +191,8 @@ export type SectCurrentData = { sect: CultivatorSectState | null; methodLevelCap: number; knownAbilityIds: string[]; - commission: { - dateKey: string; - completionType?: string; - completedAt?: string; - claimedAt?: string; - }; + overview?: SectOverviewData | null; + commission?: never; }; export type SectDetailData = { diff --git a/src/shared/engine/cultivation/CultivationEngine.test.ts b/src/shared/engine/cultivation/CultivationEngine.test.ts index f561b943..7930e49c 100644 --- a/src/shared/engine/cultivation/CultivationEngine.test.ts +++ b/src/shared/engine/cultivation/CultivationEngine.test.ts @@ -150,6 +150,17 @@ function withBodyCultivation( } describe('CultivationEngine cultivation boost', () => { + it('applies an external retreat multiplier exactly once', () => { + const base = performCultivation(createCultivator(), 10, () => 0.5); + const boosted = performCultivation(createCultivator(), 10, () => 0.5, { + retreatExpMultiplier: 1.1, + }); + + expect(boosted.summary.exp_gained).toBe( + Math.floor(base.summary.exp_gained * 1.1), + ); + }); + it('applies cultivation boost to retreat exp once and then consumes it', () => { const base = performCultivation(createCultivator(), 10, () => 0.5); const boosted = performCultivation( diff --git a/src/shared/engine/cultivation/CultivationEngine.ts b/src/shared/engine/cultivation/CultivationEngine.ts index 0f78b2e9..212ad8bb 100644 --- a/src/shared/engine/cultivation/CultivationEngine.ts +++ b/src/shared/engine/cultivation/CultivationEngine.ts @@ -155,6 +155,7 @@ export function performCultivation( rawCultivator: Cultivator, years: number, rng: () => number = Math.random, + modifiers: { retreatExpMultiplier?: number } = {}, ): CultivationResult { if (years <= 0) { throw new Error('闭关年限必须大于0'); @@ -179,6 +180,7 @@ export function performCultivation( Math.floor( expResult.exp_gained * fateContext.retreatExpMultiplier * + Math.max(1, modifiers.retreatExpMultiplier ?? 1) * getCultivationBoostRetreatMultiplier(cultivator.condition), ), ); diff --git a/src/shared/engine/sect/core/domain/index.ts b/src/shared/engine/sect/core/domain/index.ts index 4e56a06e..450470a3 100644 --- a/src/shared/engine/sect/core/domain/index.ts +++ b/src/shared/engine/sect/core/domain/index.ts @@ -1,4 +1,5 @@ export * from './compilation'; export * from './contexts'; export * from './definitions'; +export * from './organization'; export * from './state'; diff --git a/src/shared/engine/sect/core/domain/organization.ts b/src/shared/engine/sect/core/domain/organization.ts new file mode 100644 index 00000000..91c3d3c1 --- /dev/null +++ b/src/shared/engine/sect/core/domain/organization.ts @@ -0,0 +1,169 @@ +import { getRealmStageRank } from '@shared/config/realmProgression'; +import type { RealmStage, RealmType } from '@shared/types/constants'; + +export const SECT_DISCIPLE_RANKS = [ + 'registered', + 'outer', + 'inner', + 'true', +] as const; + +export type SectDiscipleRank = (typeof SECT_DISCIPLE_RANKS)[number]; +export type SectOffice = 'none' | 'steward' | 'protector' | 'elder'; + +export const SECT_FACILITY_KEYS = [ + 'archive', + 'cultivation_room', + 'workshop', + 'spirit_vein', + 'herb_garden', + 'formation', +] as const; + +export type SectFacilityKey = (typeof SECT_FACILITY_KEYS)[number]; +export type UpgradeableSectFacilityKey = Exclude; + +export const SECT_RANK_ORDER: Record = { + registered: 0, + outer: 1, + inner: 2, + true: 3, +}; + +export const SECT_RANK_LABELS: Record = { + registered: '记名弟子', + outer: '外门弟子', + inner: '内门弟子', + true: '真传弟子', +}; + +export const SECT_RANK_METHOD_CAP: Record = { + registered: 5, + outer: 20, + inner: 40, + true: Number.MAX_SAFE_INTEGER, +}; + +export const SECT_ARCHIVE_METHOD_CAP = [0, 20, 40, 60, 80, 100] as const; + +export const SECT_FACILITY_LABELS: Record = { + archive: '藏经阁', + cultivation_room: '修炼室', + workshop: '丹器坊', + spirit_vein: '灵脉', + herb_garden: '药田', + formation: '护山大阵', +}; + +export interface SectFacilityState { + key: SectFacilityKey; + level: number; + updatedAt?: string; +} + +export interface SectConstructionProjectState { + id: string; + sectId: string; + facilityKey: UpgradeableSectFacilityKey; + targetLevel: number; + progress: number; + target: number; + status: 'active' | 'completed'; + startedWeekKey: string; + completedAt?: string; +} + +export interface SectRankRequirement { + rank: SectDiscipleRank; + minRealm: RealmType; + contribution: number; + dailyCompletions?: number; + requiresTournament?: boolean; + requiresBounty?: boolean; + requiresElderTrial?: boolean; +} + +export const SECT_RANK_REQUIREMENTS: Record< + Exclude, + SectRankRequirement +> = { + outer: { + rank: 'outer', + minRealm: '炼气', + contribution: 100, + dailyCompletions: 3, + }, + inner: { + rank: 'inner', + minRealm: '筑基', + contribution: 500, + requiresTournament: true, + }, + true: { + rank: 'true', + minRealm: '金丹', + contribution: 3000, + requiresBounty: true, + requiresElderTrial: true, + }, +}; + +export function hasSectRank( + actual: SectDiscipleRank, + required: SectDiscipleRank, +): boolean { + return SECT_RANK_ORDER[actual] >= SECT_RANK_ORDER[required]; +} + +export function getEffectiveSectMethodLevelCap(args: { + realmCap: number; + rank: SectDiscipleRank; + archiveLevel: number; +}): number { + const archiveLevel = Math.max(1, Math.min(5, Math.floor(args.archiveLevel))); + return Math.min( + args.realmCap, + SECT_RANK_METHOD_CAP[args.rank], + SECT_ARCHIVE_METHOD_CAP[archiveLevel], + ); +} + +export function realmMeetsSectRank( + realm: RealmType, + stage: RealmStage, + requiredRealm: RealmType, +): boolean { + return ( + getRealmStageRank(realm, stage) >= + getRealmStageRank(requiredRealm, '初期') + ); +} + +export function getSectFacilityBonus( + key: SectFacilityKey, + level: number, +): number { + const safeLevel = Math.max(0, Math.min(5, Math.floor(level))); + switch (key) { + case 'cultivation_room': + case 'workshop': + return safeLevel * 0.02; + case 'spirit_vein': + return safeLevel * 0.05; + case 'herb_garden': + return safeLevel; + case 'archive': + return SECT_ARCHIVE_METHOD_CAP[safeLevel] ?? 0; + case 'formation': + return 0; + } +} + +export function getSectCraftDiscount( + rank: SectDiscipleRank, + workshopLevel: number, +): number { + const facilityDiscount = getSectFacilityBonus('workshop', workshopLevel); + const rankDiscount = rank === 'true' ? 0.1 : 0; + return Math.min(0.2, facilityDiscount + rankDiscount); +} diff --git a/src/shared/engine/sect/core/domain/state.ts b/src/shared/engine/sect/core/domain/state.ts index 8810a39e..3435126a 100644 --- a/src/shared/engine/sect/core/domain/state.ts +++ b/src/shared/engine/sect/core/domain/state.ts @@ -7,6 +7,7 @@ import type { SectPathLayerId, SectTacticId, } from './definitions'; +import type { SectDiscipleRank, SectOffice } from './organization'; export type SectMembershipStatus = 'prospect' | 'active'; export type SectAbilitySlots = [ @@ -38,6 +39,9 @@ export interface CultivatorSectState { joinedAt?: string; activePathId?: SectPathId; contribution: number; + discipleRank?: SectDiscipleRank; + office?: SectOffice; + promotedAt?: string; configVersion: number; methods: Partial>; paths: CultivatorSectPathState[]; diff --git a/src/shared/engine/sect/core/tests/organization.test.ts b/src/shared/engine/sect/core/tests/organization.test.ts new file mode 100644 index 00000000..3aac6fe0 --- /dev/null +++ b/src/shared/engine/sect/core/tests/organization.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; +import { + getEffectiveSectMethodLevelCap, + getSectCraftDiscount, + getSectFacilityBonus, + hasSectRank, +} from '../domain'; + +describe('宗门组织成长', () => { + it('按境界、弟子职阶和藏经阁取心法最低上限', () => { + expect(getEffectiveSectMethodLevelCap({ realmCap: 80, rank: 'registered', archiveLevel: 5 })).toBe(5); + expect(getEffectiveSectMethodLevelCap({ realmCap: 80, rank: 'outer', archiveLevel: 1 })).toBe(20); + expect(getEffectiveSectMethodLevelCap({ realmCap: 35, rank: 'inner', archiveLevel: 5 })).toBe(35); + expect(getEffectiveSectMethodLevelCap({ realmCap: 120, rank: 'true', archiveLevel: 3 })).toBe(60); + }); + + it('消费贡献不会参与职阶比较或造成降阶', () => { + expect(hasSectRank('inner', 'outer')).toBe(true); + expect(hasSectRank('outer', 'inner')).toBe(false); + }); + + it('设施加成线性成长且真传丹器折扣封顶 20%', () => { + expect(getSectFacilityBonus('cultivation_room', 5)).toBe(0.1); + expect(getSectFacilityBonus('spirit_vein', 5)).toBe(0.25); + expect(getSectCraftDiscount('inner', 5)).toBe(0.1); + expect(getSectCraftDiscount('true', 5)).toBe(0.2); + expect(getSectCraftDiscount('true', 99)).toBe(0.2); + }); +});