初始仓库迁移
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-04 23:57:06 +08:00
parent 80986b790d
commit c49c64896a
18446 changed files with 532435 additions and 2 deletions

160
src/types/attributes.ts Normal file
View File

@@ -0,0 +1,160 @@
import type {WorldType} from './core';
export const WORLD_ATTRIBUTE_SLOT_IDS = [
'axis_a',
'axis_b',
'axis_c',
'axis_d',
'axis_e',
'axis_f',
] as const;
export type WorldAttributeSlotId = typeof WORLD_ATTRIBUTE_SLOT_IDS[number];
export type LegacyAttributeKey = 'strength' | 'agility' | 'intelligence' | 'spirit';
export type LegacyAttributeSet = Record<LegacyAttributeKey, number>;
export type AttributeVector = Record<string, number>;
export interface WorldAttributeSlot {
slotId: WorldAttributeSlotId;
name: string;
definition: string;
positiveSignals: string[];
negativeSignals: string[];
combatUseText: string;
socialUseText: string;
explorationUseText: string;
}
export interface WorldAttributeSchema {
id: string;
worldId: string;
schemaVersion: number;
generatedFrom: {
worldType: WorldType;
worldName: string;
settingSummary: string;
tone: string;
conflictCore: string;
};
schemaName?: string;
slots: WorldAttributeSlot[];
}
export interface RoleAttributeEvidence {
slotId: WorldAttributeSlotId;
reason: string;
}
export interface RoleAttributeProfile {
schemaId: string;
values: Record<string, number>;
topTraits: string[];
hiddenTraits?: string[];
evidence: RoleAttributeEvidence[];
}
export type RoleRelationStance =
| 'hostile'
| 'guarded'
| 'neutral'
| 'cooperative'
| 'bonded';
export interface RoleRelationState {
affinity: number;
stance: RoleRelationStance;
}
export interface RoleResourceProfile {
maxHp: number;
maxMana: number;
}
export interface RoleResourceState extends RoleResourceProfile {
hp: number;
mana: number;
cooldowns: Record<string, number>;
}
export type RoleInteractionMode =
| 'combat'
| 'dialogue'
| 'trade'
| 'gift'
| 'recruit'
| 'explore'
| 'leave';
export interface RoleBehaviorProfile {
defaultInteractionModes?: RoleInteractionMode[];
preferredActionIds?: string[];
tabooSlots?: string[];
}
export interface RoleNarrativeProfile {
title?: string;
identity?: string;
backgroundSummary?: string;
rumorText?: string[];
faction?: string;
}
export interface NarrativeRoleEntity {
id: string;
roleKind: 'player' | 'npc' | 'monster' | 'boss' | 'companion';
worldAttributeSchemaId: string;
attributeProfile: RoleAttributeProfile;
relationState: RoleRelationState;
resourceState: RoleResourceState;
behaviorProfile: RoleBehaviorProfile;
narrativeProfile: RoleNarrativeProfile;
}
export interface RoleActionDefinition {
id: string;
name: string;
intentVector: AttributeVector;
resistVector?: AttributeVector;
baseScore: number;
category: 'combat' | 'dialogue' | 'trade' | 'gift' | 'recruit' | 'explore';
}
export interface CombatActionAttributeProfile {
intentVector: AttributeVector;
resistVector?: AttributeVector;
}
export interface DialogueActionProfile {
actionId: string;
intentVector: AttributeVector;
relationBias: 'warm' | 'pressure' | 'truth' | 'distance';
}
export interface ItemAttributeResonance {
resonanceVector: AttributeVector;
explanation?: string;
}
export interface AttributeMigrationTrace {
sourceCharacterId: string;
schemaId: string;
oldAttributes?: LegacyAttributeSet;
inferredReasons: string[];
fallbackUsed: boolean;
}
export interface AttributeSchemaGenerationInput {
worldType: WorldType;
worldName: string;
settingText: string;
summary: string;
tone: string;
playerGoal: string;
majorFactions: string[];
coreConflicts: string[];
}
export interface AttributeSchemaGenerationOutput {
schemaName: string;
slots: WorldAttributeSlot[];
}

23
src/types/build.ts Normal file
View File

@@ -0,0 +1,23 @@
import type {AttributeVector} from './attributes';
export type BuildTagCategory = '流派' | '风格' | '资源' | '防御' | '元素' | '工艺';
export type BuildBuffSourceType = 'skill' | 'item' | 'forge';
export interface BuildTagDefinition {
id: string;
label: string;
category: BuildTagCategory;
aliases: string[];
description: string;
attributeAffinity: AttributeVector;
}
export interface TimedBuildBuff {
id: string;
sourceType: BuildBuffSourceType;
sourceId: string;
name: string;
tags: string[];
durationTurns: number;
maxStacks?: number;
}

155
src/types/characters.ts Normal file
View File

@@ -0,0 +1,155 @@
import type {
CombatActionAttributeProfile,
LegacyAttributeSet,
RoleAttributeProfile,
RoleResourceProfile,
} from './attributes';
import type {TimedBuildBuff} from './build';
import {
AnimationState,
type CharacterAnimationConfig,
type CharacterGender,
type CombatDelivery,
type ConversationGuardStyle,
type ConversationTruthStyle,
type ConversationWarmStyle,
type SkillEffectAnchor,
type SkillEffectMotion,
type SkillEffectPhase,
type SkillStyle,
WorldType,
} from './core';
export type SpriteSequenceDefinition =
| {
source: 'animation';
animation: AnimationState;
fps?: number;
}
| {
source: 'asset';
folder: string;
prefix?: string;
frames?: number;
startFrame?: number;
extension?: string;
file?: string;
fps?: number;
};
export interface CharacterSkillEffectDefinition {
phase: SkillEffectPhase;
anchor: SkillEffectAnchor;
motion?: SkillEffectMotion;
sequence: SpriteSequenceDefinition;
durationMs?: number;
scale?: number;
sizePx?: number;
startOffsetX?: number;
endOffsetX?: number;
startYOffset?: number;
endYOffset?: number;
}
export interface CharacterSkillDefinition {
id: string;
name: string;
animation: AnimationState;
casterAnimation?: AnimationState;
damage: number;
manaCost: number;
cooldownTurns: number;
range: number;
style: SkillStyle;
delivery?: CombatDelivery;
releaseDelayMs?: number;
buildBuffs?: TimedBuildBuff[];
effects?: CharacterSkillEffectDefinition[];
attributeProfile?: CombatActionAttributeProfile | null;
}
export interface CharacterAdventureOpening {
reason: string;
goal: string;
monologue: string;
surfaceHook?: string;
immediateConcern?: string;
guardedMotive?: string;
}
export interface CharacterBackstoryChapter {
id: string;
title: string;
affinityRequired: number;
teaser: string;
content: string;
contextSnippet: string;
}
export interface CharacterBackstoryRevealConfig {
publicSummary: string;
chapters: CharacterBackstoryChapter[];
privateChatUnlockAffinity?: number;
}
export interface CharacterConversationStyle {
guardStyle: ConversationGuardStyle;
warmStyle: ConversationWarmStyle;
truthStyle: ConversationTruthStyle;
}
export interface GeneratedCharacterVisualAsset {
id: string;
characterId: string;
sourceMode: 'text-to-image' | 'image-to-image' | 'upload';
promptText?: string;
masterImagePath: string;
previewImagePaths: string[];
width: number;
height: number;
facing: 'right';
locked: boolean;
}
export interface GeneratedCharacterAnimationAsset {
id: string;
animationSetId: string;
characterId: string;
visualAssetId: string;
action: string;
frameCount: number;
fps: number;
loop: boolean;
frameWidth: number;
frameHeight: number;
previewVideoPath?: string;
spriteSheetPath?: string;
framePaths: string[];
}
export interface Character {
id: string;
name: string;
title: string;
gender?: CharacterGender;
description: string;
backstory: string;
backstoryReveal?: CharacterBackstoryRevealConfig;
avatar: string;
portrait: string;
assetFolder: string;
assetVariant: string;
generatedVisualAssetId?: string;
generatedAnimationSetId?: string;
groundOffsetY?: number;
animationMap?: Partial<Record<AnimationState, CharacterAnimationConfig>>;
attributes: LegacyAttributeSet;
attributeProfile?: RoleAttributeProfile;
attributeProfiles?: Partial<Record<WorldType, RoleAttributeProfile>>;
resourceProfile?: RoleResourceProfile;
personality: string;
combatTags?: string[];
conversationStyle?: CharacterConversationStyle;
skills: CharacterSkillDefinition[];
adventureOpenings: Partial<Record<WorldType, CharacterAdventureOpening>>;
}

93
src/types/core.ts Normal file
View File

@@ -0,0 +1,93 @@
export enum WorldType {
WUXIA = 'WUXIA',
XIANXIA = 'XIANXIA',
CUSTOM = 'CUSTOM',
}
export type WorldTemplateType = Exclude<WorldType, WorldType.CUSTOM>;
export enum AnimationState {
IDLE = 'idle',
ACQUIRE = 'acquire',
ATTACK = 'attack',
RUN = 'run',
JUMP = 'jump',
DOUBLE_JUMP = 'double jump',
JUMP_ATTACK = 'jump attack',
DASH = 'dash',
HURT = 'hurt',
DIE = 'die',
CLIMB = 'climb',
SKILL1 = 'skill1',
SKILL1_JUMP = 'skill1 jump',
SKILL1_BULLET = 'skill1 bullet',
SKILL1_BULLET_FX = 'skill1 bullet FX',
SKILL2 = 'skill2',
SKILL2_JUMP = 'skill2 jump',
SKILL3 = 'skill3',
SKILL3_JUMP = 'skill3 jump',
SKILL3_BULLET = 'skill3 bullet',
SKILL3_BULLET_FX = 'skill3 bullet FX',
SKILL4 = 'skill4',
WALL_SLIDE = 'Wall Slide',
}
export interface CharacterAnimationConfig {
folder: string;
prefix: string;
frames: number;
startFrame?: number;
extension?: string;
file?: string;
basePath?: string;
}
export type SkillStyle = 'burst' | 'steady' | 'mobility' | 'finisher' | 'projectile';
export type PlayerStateMode = 'battle' | 'idle';
export type FunctionCategory = 'battle' | 'escape' | 'idle' | 'recovery';
export type ItemRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';
export type EquipmentSlotId = 'weapon' | 'armor' | 'relic';
export type CharacterGender = 'male' | 'female' | 'unknown';
export type ItemWorldAffinity = 'neutral' | 'wuxia' | 'xianxia';
export type ConversationGuardStyle = 'blunt' | 'wary' | 'evasive' | 'measured';
export type ConversationWarmStyle = 'dry' | 'steady' | 'gentle' | 'teasing';
export type ConversationTruthStyle = 'direct' | 'fragmented' | 'deflecting';
export type NpcDisclosureStage = 'guarded' | 'partial' | 'honest' | 'deep';
export type NpcWarmthStage = 'distant' | 'neutral' | 'cooperative' | 'warm';
export type NpcAnswerMode = 'situational_only' | 'half_truth' | 'true_but_incomplete' | 'candid';
export type ConversationSituation =
| 'first_contact_cautious'
| 'camp_first_contact'
| 'camp_followup'
| 'post_battle_breath'
| 'shared_danger_coordination'
| 'private_followup';
export type ConversationPressure = 'high' | 'medium' | 'low';
export type NpcFunctionType = 'trade' | 'fight' | 'spar' | 'help' | 'chat' | 'recruit' | 'gift' | 'quest';
export type QuestObjectiveKind =
| 'defeat_hostile_npc'
| 'inspect_treasure'
| 'spar_with_npc'
| 'talk_to_npc'
| 'reach_scene'
| 'deliver_item';
export type QuestStatus =
| 'discovered'
| 'active'
| 'ready_to_turn_in'
| 'completed'
| 'turned_in'
| 'failed'
| 'expired';
export type NpcInteractionAction = NpcFunctionType | 'leave' | 'quest_accept' | 'quest_turn_in';
export type TreasureInteractionAction = 'secure' | 'inspect' | 'leave';
export type NpcBattleMode = 'fight' | 'spar';
export type NpcBattleOutcome = 'fight_victory' | 'spar_complete';
export type CombatDelivery = 'melee' | 'ranged';
export type CombatActionMode = 'idle' | CombatDelivery;
export type SkillEffectPhase = 'cast' | 'travel' | 'impact';
export type SkillEffectAnchor = 'caster' | 'target';
export type SkillEffectMotion = 'stationary' | 'projectile';
export type HostileNpcRenderAnimation = 'idle' | 'move' | 'attack' | 'die';
export type MonsterRenderAnimation = HostileNpcRenderAnimation;
export type FacingDirection = 'left' | 'right';

100
src/types/customWorld.ts Normal file
View File

@@ -0,0 +1,100 @@
import type {
ItemAttributeResonance,
RoleAttributeProfile,
WorldAttributeSchema,
} from './attributes';
import {
type EquipmentSlotId,
type ItemRarity,
type WorldTemplateType,
} from './core';
import type {ItemStatProfile, ItemUseProfile} from './items';
export interface CustomWorldPlayableNpc {
id: string;
name: string;
title: string;
description: string;
backstory: string;
personality: string;
combatStyle: string;
tags: string[];
templateCharacterId?: string;
attributeProfile?: RoleAttributeProfile;
}
export type CustomWorldNpcVisualRace = 'human' | 'elf' | 'orc' | 'goblin';
export type CustomWorldNpcVisualGearType = 'cloth' | 'leather' | 'metal' | 'melee' | 'magic' | 'ranged';
export interface CustomWorldNpcVisualGear {
type: CustomWorldNpcVisualGearType;
file: string;
frameIndex: number;
}
export interface CustomWorldNpcVisual {
race: CustomWorldNpcVisualRace;
bodyColor: string;
headIndex: number;
hairColorIndex: number;
hairStyleFrame: number;
facialHairEnabled: boolean;
facialHairColorIndex: number;
facialHairStyleFrame: number;
headgear?: CustomWorldNpcVisualGear | null;
mainHand?: CustomWorldNpcVisualGear | null;
offHand?: CustomWorldNpcVisualGear | null;
}
export interface CustomWorldNpc {
id: string;
name: string;
role: string;
description: string;
motivation: string;
relationshipHooks: string[];
imageSrc?: string;
visual?: CustomWorldNpcVisual;
attributeProfile?: RoleAttributeProfile;
}
export interface CustomWorldItem {
id: string;
name: string;
category: string;
rarity: ItemRarity;
description: string;
tags: string[];
iconSrc?: string;
sourcePath?: string;
origin?: 'generated' | 'catalog';
equipmentSlotId?: EquipmentSlotId | null;
statProfile?: ItemStatProfile | null;
useProfile?: ItemUseProfile | null;
value?: number;
attributeResonance?: ItemAttributeResonance | null;
}
export interface CustomWorldLandmark {
id: string;
name: string;
description: string;
dangerLevel: string;
imageSrc?: string;
}
export interface CustomWorldProfile {
id: string;
settingText: string;
name: string;
subtitle: string;
summary: string;
tone: string;
playerGoal: string;
templateWorldType: WorldTemplateType;
attributeSchema: WorldAttributeSchema;
playableNpcs: CustomWorldPlayableNpc[];
storyNpcs: CustomWorldNpc[];
items: CustomWorldItem[];
landmarks: CustomWorldLandmark[];
}

82
src/types/game.ts Normal file
View File

@@ -0,0 +1,82 @@
import type {TimedBuildBuff} from './build';
import type {Character} from './characters';
import {
AnimationState,
type CombatActionMode,
type NpcBattleMode,
type NpcBattleOutcome,
WorldType,
} from './core';
import type {CustomWorldProfile} from './customWorld';
import type {EquipmentLoadout, InventoryItem} from './items';
import type {
CombatVisualEffect,
CompanionState,
Encounter,
NpcPersistentState,
SceneEncounterResult,
SceneHostileNpc,
ScenePresetInfo,
} from './scene';
import type {CharacterChatRecord, QuestLogEntry, StoryMoment} from './story';
export interface GameRuntimeStats {
playTimeMs: number;
lastPlayTickAt: string | null;
hostileNpcsDefeated: number;
questsAccepted: number;
itemsUsed: number;
scenesTraveled: number;
}
export interface GameState {
worldType: WorldType | null;
customWorldProfile: CustomWorldProfile | null;
playerCharacter: Character | null;
runtimeStats: GameRuntimeStats;
currentScene: string;
storyHistory: StoryMoment[];
characterChats: Record<string, CharacterChatRecord>;
ambientIdleMode?: 'observe_signs';
lastObserveSignsSceneId?: string | null;
lastObserveSignsReport?: string | null;
animationState: AnimationState;
currentEncounter: Encounter | null;
npcInteractionActive: boolean;
currentScenePreset: ScenePresetInfo | null;
sceneMonsters: SceneHostileNpc[];
sceneHostileNpcs?: SceneHostileNpc[];
playerX: number;
playerOffsetY: number;
playerFacing: 'left' | 'right';
playerActionMode: CombatActionMode;
scrollWorld: boolean;
inBattle: boolean;
playerHp: number;
playerMaxHp: number;
playerMana: number;
playerMaxMana: number;
playerSkillCooldowns: Record<string, number>;
activeBuildBuffs?: TimedBuildBuff[];
activeCombatEffects: CombatVisualEffect[];
playerCurrency: number;
playerInventory: InventoryItem[];
playerEquipment: EquipmentLoadout;
npcStates: Record<string, NpcPersistentState>;
quests: QuestLogEntry[];
roster: CompanionState[];
companions: CompanionState[];
currentBattleNpcId: string | null;
currentNpcBattleMode: NpcBattleMode | null;
currentNpcBattleOutcome: NpcBattleOutcome | null;
sparReturnEncounter: Encounter | null;
sparPlayerHpBefore: number | null;
sparPlayerMaxHpBefore: number | null;
sparStoryHistoryBefore: StoryMoment[] | null;
}
export interface AIResponse {
storyText: string;
options: StoryMoment['options'];
encounter?: SceneEncounterResult;
}

110
src/types/items.ts Normal file
View File

@@ -0,0 +1,110 @@
import type {AttributeVector, ItemAttributeResonance} from './attributes';
import type {TimedBuildBuff} from './build';
import {
type EquipmentSlotId,
type ItemRarity,
type ItemWorldAffinity,
WorldType,
} from './core';
import type {RuntimeItemMetadata} from './runtimeItem';
export interface InventoryItem {
id: string;
category: string;
name: string;
quantity: number;
rarity: ItemRarity;
tags: string[];
catalogId?: string;
iconSrc?: string;
description?: string;
worldAffinity?: ItemWorldAffinity;
equipmentSlotId?: EquipmentSlotId | null;
worldProfiles?: Partial<Record<WorldType, ItemWorldProfile>>;
statProfile?: ItemStatProfile | null;
useProfile?: ItemUseProfile | null;
buildProfile?: ItemBuildProfile | null;
value?: number;
attributeResonance?: ItemAttributeResonance | null;
runtimeMetadata?: RuntimeItemMetadata | null;
}
export interface MonsterLootEntry {
id: string;
item: InventoryItem;
dropRate: number;
}
export interface ItemCatalogEntry {
id: string;
sourcePath: string;
iconSrc: string;
name: string;
category: string;
rarity: ItemRarity;
tags: string[];
description: string;
worldAffinity: ItemWorldAffinity;
equipmentSlotId?: EquipmentSlotId | null;
worldProfiles?: Partial<Record<WorldType, ItemWorldProfile>>;
statProfile?: ItemStatProfile | null;
useProfile?: ItemUseProfile | null;
buildProfile?: ItemBuildProfile | null;
value?: number;
attributeResonance?: ItemAttributeResonance | null;
runtimeMetadata?: RuntimeItemMetadata | null;
}
export interface ItemCatalogOverride {
name?: string;
category?: string;
rarity?: ItemRarity;
tags?: string[];
description?: string;
worldAffinity?: ItemWorldAffinity;
equipmentSlotId?: EquipmentSlotId | null;
worldProfiles?: Partial<Record<WorldType, ItemWorldProfile>>;
statProfile?: ItemStatProfile | null;
useProfile?: ItemUseProfile | null;
buildProfile?: ItemBuildProfile | null;
value?: number;
attributeResonance?: ItemAttributeResonance | null;
runtimeMetadata?: RuntimeItemMetadata | null;
}
export interface ItemWorldProfile {
name: string;
description: string;
}
export interface ItemStatProfile {
maxHpBonus?: number;
maxManaBonus?: number;
outgoingDamageBonus?: number;
incomingDamageMultiplier?: number;
}
export interface ItemUseProfile {
hpRestore?: number;
manaRestore?: number;
cooldownReduction?: number;
buildBuffs?: TimedBuildBuff[];
}
export interface ItemBuildProfile {
role: string;
tags: string[];
setId?: string;
setName?: string;
pieceName?: string;
synergy?: string[];
craftTags?: string[];
forgeRank?: number;
resonanceVector?: AttributeVector;
}
export interface EquipmentLoadout {
weapon: InventoryItem | null;
armor: InventoryItem | null;
relic: InventoryItem | null;
}

1
src/types/navigation.ts Normal file
View File

@@ -0,0 +1 @@
export type BottomTab = 'character' | 'adventure' | 'inventory';

108
src/types/runtimeItem.ts Normal file
View File

@@ -0,0 +1,108 @@
import type {WorldType} from './core';
import type {CustomWorldProfile} from './customWorld';
import type {InventoryItem} from './items';
import type {Encounter, NpcPersistentState, ScenePresetInfo} from './scene';
export type RuntimeItemGenerationChannel =
| 'treasure'
| 'npc_trade'
| 'npc_reward'
| 'monster_drop'
| 'quest_reward'
| 'discovery';
export type RuntimeItemOrigin = 'catalog' | 'procedural' | 'ai_compiled';
export type RuntimeItemKind = 'equipment' | 'consumable' | 'material' | 'relic' | 'quest';
export type RuntimeItemPermanence = 'permanent' | 'timed' | 'resource';
export type RuntimeNarrativeWeight = 'light' | 'medium' | 'heavy';
export type RuntimeItemPlanSlot = 'primary' | 'secondary' | 'support';
export type RuntimeRelationAnchor =
| {type: 'npc'; npcId?: string; npcName: string; roleText?: string}
| {type: 'scene'; sceneId?: string; sceneName: string}
| {type: 'landmark'; landmarkName: string}
| {type: 'monster'; monsterId?: string; monsterName: string}
| {type: 'faction'; factionName: string}
| {type: 'quest'; questId?: string; questName: string};
export interface RuntimeItemMetadata {
origin: RuntimeItemOrigin;
generationChannel: RuntimeItemGenerationChannel;
seedKey: string;
relationAnchor?: RuntimeRelationAnchor;
sourceReason: string;
recentEventHook?: string;
}
export interface RuntimeItemGenerationContext {
worldType: WorldType | null;
customWorldProfile: CustomWorldProfile | null;
sceneId: string | null;
sceneName: string | null;
sceneDescription: string | null;
sceneTags: string[];
treasureHints: string[];
encounter: Encounter | null;
encounterNpcId: string | null;
encounterNpcName: string | null;
encounterContextText: string | null;
relatedNpcState: NpcPersistentState | null;
relatedScene: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
recentStorySummary: string;
recentActions: string[];
playerCharacterId: string;
playerBuildTags: string[];
playerBuildGaps: string[];
playerEquipmentTags: string[];
generationChannel: RuntimeItemGenerationChannel;
}
export interface RuntimeItemPlan {
slot: RuntimeItemPlanSlot;
itemKind: RuntimeItemKind;
permanence: RuntimeItemPermanence;
narrativeWeight: RuntimeNarrativeWeight;
targetBuildDirection: string[];
relationAnchor: RuntimeRelationAnchor;
}
export interface RuntimeItemAiPromptInput {
worldSummary: string;
sceneSummary: string;
encounterSummary: string;
relatedNpcSummary: string;
recentStorySummary: string;
generationChannel: RuntimeItemGenerationChannel;
playerBuildDirection: string[];
playerBuildGaps: string[];
desiredItemKind: string;
permanence: string;
}
export interface RuntimeItemAiIntent {
shortNameSeed: string;
sourcePhrase: string;
reasonToAppear: string;
relationHooks: string[];
desiredBuildTags: string[];
desiredFunctionalBias: Array<'heal' | 'mana' | 'cooldown' | 'guard' | 'damage'>;
tone: 'grim' | 'mysterious' | 'martial' | 'ritual' | 'survival';
}
export interface RuntimeItemCompileBudget {
rarity: InventoryItem['rarity'];
buildTagLimit: number;
timedBuffTagLimit: number;
timedBuffDuration: number;
statBudgetTier: number;
}
export interface DirectedRuntimeReward {
primaryItem?: InventoryItem | null;
supportItems: InventoryItem[];
hp?: number;
mana?: number;
currency?: number;
storyHint?: string;
}

190
src/types/scene.ts Normal file
View File

@@ -0,0 +1,190 @@
import type {
RoleActionDefinition,
RoleAttributeProfile,
RoleRelationState,
} from './attributes';
import type {Character} from './characters';
import {
AnimationState,
type CharacterGender,
type CombatActionMode,
type CombatDelivery,
type FacingDirection,
type HostileNpcRenderAnimation,
type NpcFunctionType,
} from './core';
import type {InventoryItem} from './items';
export interface NpcPersistentState {
affinity: number;
relationState?: RoleRelationState;
helpUsed: boolean;
chattedCount: number;
giftsGiven: number;
inventory: InventoryItem[];
recruited: boolean;
revealedFacts?: string[];
knownAttributeRumors?: string[];
firstMeaningfulContactResolved?: boolean;
seenBackstoryChapterIds?: string[];
}
export interface CompanionState {
npcId: string;
characterId: string;
joinedAtAffinity: number;
hp: number;
maxHp: number;
mana: number;
maxMana: number;
skillCooldowns: Record<string, number>;
animationState?: AnimationState;
actionMode?: CombatActionMode;
offsetX?: number;
offsetY?: number;
transitionMs?: number;
}
export interface CompanionRenderState {
npcId: string;
character: Character;
hp: number;
maxHp: number;
mana: number;
maxMana: number;
skillCooldowns: Record<string, number>;
animationState: AnimationState;
actionMode?: CombatActionMode;
slot: 'upper' | 'lower';
facing?: FacingDirection;
entryOffsetX?: number;
entryOffsetY?: number;
transitionMs?: number;
recruitToken?: number;
}
export interface Encounter {
id?: string;
kind?: 'npc' | 'treasure';
npcName: string;
npcDescription: string;
npcAvatar: string;
context: string;
gender?: CharacterGender;
specialBehavior?: 'initial_companion' | 'camp_companion';
xMeters?: number;
characterId?: string;
hostileNpcPresetId?: string;
monsterPresetId?: string;
initialAffinity?: number;
hostile?: boolean;
attributeProfile?: RoleAttributeProfile;
}
export interface SceneHostileNpc {
id: string;
name: string;
action: string;
description: string;
animation: HostileNpcRenderAnimation;
xMeters: number;
yOffset: number;
facing: FacingDirection;
attackRange: number;
speed: number;
hp: number;
maxHp: number;
renderKind?: 'npc';
encounter?: Encounter;
characterAnimation?: AnimationState;
combatMode?: CombatDelivery;
combatTags?: string[];
attributeProfile?: RoleAttributeProfile;
behaviorVectors?: RoleActionDefinition[];
}
export type SceneMonster = SceneHostileNpc;
export interface SceneNpc {
id: string;
name: string;
description: string;
avatar: string;
role: string;
gender?: CharacterGender;
characterId?: string;
hostileNpcPresetId?: string;
monsterPresetId?: string;
initialAffinity?: number;
hostile?: boolean;
functions?: NpcFunctionType[];
recruitable?: boolean;
attributeProfile?: RoleAttributeProfile;
}
export type SceneEncounterKind = 'npc' | 'treasure' | 'none';
export interface SceneEncounterResult {
kind: SceneEncounterKind;
npcId?: string;
treasureText?: string;
}
export interface CombatVisualEffect {
id: string;
frames: string[];
fps: number;
startX: number;
endX?: number;
startOrigin: 'player' | 'hostile_npc' | 'monster';
endOrigin?: 'player' | 'hostile_npc' | 'monster';
startMonsterId?: string;
endMonsterId?: string;
startHostileNpcId?: string;
endHostileNpcId?: string;
startAnchorOffsetY?: number;
endAnchorOffsetY?: number;
startOffsetX?: number;
endOffsetX?: number;
startYOffset: number;
endYOffset?: number;
durationMs: number;
sizePx: number;
scale?: number;
facing: FacingDirection;
zIndex?: number;
traveling?: boolean;
}
export interface SceneHostileNpcChange {
id: string;
action: string;
animation: HostileNpcRenderAnimation;
moveMeters?: number;
yOffset?: number;
}
export type SceneMonsterChange = SceneHostileNpcChange;
export interface SceneDirective {
playerAnimation: AnimationState;
playerMoveMeters: number;
playerOffsetY: number;
playerFacing: FacingDirection;
scrollWorld: boolean;
monsterChanges: SceneMonsterChange[];
hostileNpcChanges?: SceneHostileNpcChange[];
}
export interface ScenePresetInfo {
id: string;
name: string;
description: string;
imageSrc: string;
forwardSceneId?: string;
connectedSceneIds?: string[];
hostileNpcIds?: string[];
monsterIds?: string[];
npcs?: SceneNpc[];
treasureHints?: string[];
}

127
src/types/story.ts Normal file
View File

@@ -0,0 +1,127 @@
import {
type NpcInteractionAction,
type QuestObjectiveKind,
type QuestStatus,
type TreasureInteractionAction,
} from './core';
import type {InventoryItem} from './items';
import type {SceneDirective} from './scene';
export interface StoryOption {
functionId: string;
actionText: string;
text?: string;
detailText?: string;
priority?: number;
visuals: SceneDirective;
skillProbabilities?: Record<string, number>;
interaction?: StoryOptionInteraction;
}
export interface QuestReward {
affinityBonus: number;
currency: number;
items: InventoryItem[];
intel?: {
codexEntry?: string;
rumorText?: string;
unlockedSceneId?: string;
};
}
export interface QuestObjective {
kind: QuestObjectiveKind;
targetHostileNpcId?: string;
targetNpcId?: string;
targetSceneId?: string;
targetItemId?: string;
requiredCount: number;
}
export type QuestNarrativeType =
| 'bounty'
| 'escort'
| 'investigation'
| 'retrieval'
| 'relationship'
| 'trial';
export interface QuestNarrativeBinding {
origin: 'fallback_builder' | 'ai_compiled';
narrativeType: QuestNarrativeType;
dramaticNeed: string;
issuerGoal: string;
playerHook: string;
worldReason: string;
followupHooks: string[];
}
export interface QuestStep extends QuestObjective {
id: string;
title: string;
progress: number;
revealText: string;
completeText: string;
}
export interface QuestLogEntry {
id: string;
issuerNpcId: string;
issuerNpcName: string;
sceneId: string | null;
title: string;
description: string;
summary: string;
objective: QuestObjective;
progress: number;
status: QuestStatus;
completionNotified?: boolean;
reward: QuestReward;
rewardText: string;
narrativeBinding?: QuestNarrativeBinding;
steps?: QuestStep[];
activeStepId?: string | null;
visibleStage?: number;
hiddenFlags?: string[];
}
export interface StoryDialogueTurn {
speaker: 'player' | 'npc' | 'companion';
speakerName?: string;
text: string;
}
export interface CharacterChatTurn {
speaker: 'player' | 'character';
text: string;
}
export interface CharacterChatRecord {
history: CharacterChatTurn[];
summary: string;
updatedAt: string | null;
}
export type StoryHistoryRole = 'action' | 'result';
export interface StoryMoment {
text: string;
options: StoryOption[];
displayMode?: 'narrative' | 'dialogue';
dialogue?: StoryDialogueTurn[];
streaming?: boolean;
deferredOptions?: StoryOption[];
historyRole?: StoryHistoryRole;
}
export type StoryOptionInteraction =
| {
kind: 'npc';
npcId: string;
action: NpcInteractionAction;
questId?: string;
}
| {
kind: 'treasure';
action: TreasureInteractionAction;
};