@causyne/agent 0.0.0-stage → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const gameEconomyBalancer = {
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'game.economy_balancer',
|
|
8
|
+
description: 'Calculates and balances game economy mechanics: enemy stat scaling curves across levels (`enemy_scaling.json`), item drop tables (`loot_tables.json`), item pricing formulas, and player XP/level progression thresholds.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['balance_enemy_scaling', 'generate_loot_table', 'balance_xp_curve', 'save_economy_data'],
|
|
15
|
+
description: 'Action: balance_enemy_scaling (compute HP/attack across levels), generate_loot_table (assign drop probabilities), balance_xp_curve (level thresholds), or save_economy_data (write database files).'
|
|
16
|
+
},
|
|
17
|
+
maxLevel: { type: 'number', description: 'Maximum player/enemy level to simulate (default 50).' },
|
|
18
|
+
difficultyCurve: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['linear', 'exponential', 'souls_hardcore'],
|
|
21
|
+
description: 'Scaling curve type.'
|
|
22
|
+
},
|
|
23
|
+
baseHp: { type: 'number', description: 'Level 1 base HP (default 100).' },
|
|
24
|
+
baseXp: { type: 'number', description: 'Level 1 base XP required for level 2 (default 500).' },
|
|
25
|
+
lootEntries: {
|
|
26
|
+
type: 'array',
|
|
27
|
+
items: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
item_id: { type: 'string' },
|
|
31
|
+
drop_chance_pct: { type: 'number' },
|
|
32
|
+
min_qty: { type: 'number' },
|
|
33
|
+
max_qty: { type: 'number' }
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
description: 'Loot table items and percentage drop chances.'
|
|
37
|
+
},
|
|
38
|
+
projectDir: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'Absolute path to project directory (`data/` folder).'
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ['action']
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async execute(args) {
|
|
48
|
+
try {
|
|
49
|
+
const { action, maxLevel = 50, difficultyCurve = 'exponential', baseHp = 100, baseXp = 500, lootEntries = [], projectDir } = args;
|
|
50
|
+
|
|
51
|
+
if (action === 'balance_enemy_scaling') {
|
|
52
|
+
const scalingTable = [];
|
|
53
|
+
for (let lvl = 1; lvl <= maxLevel; lvl++) {
|
|
54
|
+
let multiplier = 1.0;
|
|
55
|
+
if (difficultyCurve === 'linear') {
|
|
56
|
+
multiplier = 1 + (lvl - 1) * 0.15;
|
|
57
|
+
} else if (difficultyCurve === 'souls_hardcore') {
|
|
58
|
+
multiplier = Math.pow(1.22, lvl - 1);
|
|
59
|
+
} else {
|
|
60
|
+
// exponential
|
|
61
|
+
multiplier = Math.pow(1.15, lvl - 1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
scalingTable.push({
|
|
65
|
+
level: lvl,
|
|
66
|
+
max_hp: Math.round(baseHp * multiplier),
|
|
67
|
+
attack_damage: Math.round((baseHp * 0.15) * multiplier),
|
|
68
|
+
xp_reward: Math.round((baseXp * 0.2) * Math.pow(1.12, lvl - 1)),
|
|
69
|
+
gold_drop: Math.round(15 * Math.pow(1.1, lvl - 1))
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
74
|
+
const dataDir = path.join(projectDir, 'data');
|
|
75
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
76
|
+
await fs.writeFile(path.join(dataDir, 'enemy_scaling.json'), JSON.stringify(scalingTable, null, 2), 'utf8');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
success: true,
|
|
81
|
+
output: JSON.stringify({
|
|
82
|
+
status: 'BALANCED_ENEMY_SCALING',
|
|
83
|
+
max_level: maxLevel,
|
|
84
|
+
curve: difficultyCurve,
|
|
85
|
+
sample_levels: [scalingTable[0], scalingTable[9], scalingTable[24], scalingTable[maxLevel - 1]],
|
|
86
|
+
total_rows: scalingTable.length
|
|
87
|
+
}, null, 2)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (action === 'generate_loot_table') {
|
|
92
|
+
const table = lootEntries.length > 0 ? lootEntries : [
|
|
93
|
+
{ item_id: 'gold_coins', drop_chance_pct: 85.0, min_qty: 10, max_qty: 45 },
|
|
94
|
+
{ item_id: 'health_potion', drop_chance_pct: 40.0, min_qty: 1, max_qty: 2 },
|
|
95
|
+
{ item_id: 'shadow_shard', drop_chance_pct: 25.0, min_qty: 1, max_qty: 3 },
|
|
96
|
+
{ item_id: 'iron_sword', drop_chance_pct: 8.0, min_qty: 1, max_qty: 1 },
|
|
97
|
+
{ item_id: 'relic_silver_key', drop_chance_pct: 2.5, min_qty: 1, max_qty: 1 }
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
101
|
+
const dataDir = path.join(projectDir, 'data');
|
|
102
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
103
|
+
await fs.writeFile(path.join(dataDir, 'loot_tables.json'), JSON.stringify({ default_enemy_loot: table }, null, 2), 'utf8');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
output: JSON.stringify({
|
|
109
|
+
status: 'GENERATED_LOOT_TABLE',
|
|
110
|
+
entries_count: table.length,
|
|
111
|
+
table: table
|
|
112
|
+
}, null, 2)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (action === 'balance_xp_curve') {
|
|
117
|
+
const xpCurve = [];
|
|
118
|
+
let totalAccumulated = 0;
|
|
119
|
+
for (let lvl = 1; lvl <= maxLevel; lvl++) {
|
|
120
|
+
const req = Math.round(baseXp * Math.pow(1.18, lvl - 1));
|
|
121
|
+
totalAccumulated += req;
|
|
122
|
+
xpCurve.push({
|
|
123
|
+
level: lvl,
|
|
124
|
+
xp_to_next: req,
|
|
125
|
+
total_xp_required: totalAccumulated
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
130
|
+
const dataDir = path.join(projectDir, 'data');
|
|
131
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
132
|
+
await fs.writeFile(path.join(dataDir, 'xp_curve.json'), JSON.stringify(xpCurve, null, 2), 'utf8');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
success: true,
|
|
137
|
+
output: JSON.stringify({
|
|
138
|
+
status: 'BALANCED_XP_CURVE',
|
|
139
|
+
max_level: maxLevel,
|
|
140
|
+
base_xp: baseXp,
|
|
141
|
+
sample_levels: [xpCurve[0], xpCurve[9], xpCurve[24], xpCurve[maxLevel - 1]]
|
|
142
|
+
}, null, 2)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (action === 'save_economy_data') {
|
|
147
|
+
if (!projectDir || !fsSync.existsSync(projectDir)) {
|
|
148
|
+
return { success: false, error: `projectDir is required to save economy data.` };
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
success: true,
|
|
152
|
+
output: JSON.stringify({ status: 'SAVED_ALL_ECONOMY_DATA', project_dir: projectDir }, null, 2)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
157
|
+
} catch (e) {
|
|
158
|
+
return { success: false, error: e.message };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const gameEntitySpawner = {
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'game.entity_spawner',
|
|
8
|
+
description: 'Generates ready-to-instance game entity prefabs (`.tscn` + `.gd` scripts) for Godot 4.x. Includes templates for Player Hero 8-way movement, Skeleton Warrior AI, Boss Necromancer, Cyberpunk Race Car drift physics, Lockable Treasure Chest, and Inter-biome Portal Gate.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['spawn_prefab', 'list_prefabs', 'batch_spawn_entities'],
|
|
15
|
+
description: 'Action: spawn_prefab (generate specific prefab .tscn and .gd script), list_prefabs (catalog available entity templates), or batch_spawn_entities (generate all core entities into project).'
|
|
16
|
+
},
|
|
17
|
+
prefabType: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
enum: ['player_hero_2d', 'player_hero_3d', 'skeleton_warrior_ai', 'boss_necromancer', 'cyberpunk_racer_car', 'treasure_chest', 'portal_gate'],
|
|
20
|
+
description: 'Type of entity template to generate.'
|
|
21
|
+
},
|
|
22
|
+
entityName: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Custom node name for the spawned entity.'
|
|
25
|
+
},
|
|
26
|
+
projectDir: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Absolute path to Godot project directory (`res://...`) where `scenes/actors` and `scripts/actors` reside.'
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
required: ['action']
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async execute(args) {
|
|
36
|
+
try {
|
|
37
|
+
const { action, prefabType = 'player_hero_2d', entityName, projectDir } = args;
|
|
38
|
+
|
|
39
|
+
if (action === 'list_prefabs') {
|
|
40
|
+
return {
|
|
41
|
+
success: true,
|
|
42
|
+
output: JSON.stringify({
|
|
43
|
+
status: 'PREFAB_CATALOG',
|
|
44
|
+
prefabs: [
|
|
45
|
+
{ type: 'player_hero_2d', desc: '8-way CharacterBody2D movement with sword attack, dash stamina, and Sprite2D/AnimationPlayer.' },
|
|
46
|
+
{ type: 'player_hero_3d', desc: '3D CharacterBody3D third-person controller with camera springarm and gravity.' },
|
|
47
|
+
{ type: 'skeleton_warrior_ai', desc: 'Melee enemy with patrol/chase/attack AI and loot drop signal.' },
|
|
48
|
+
{ type: 'boss_necromancer', desc: 'Multi-phase boss controller with spell casting and skeleton summoning triggers.' },
|
|
49
|
+
{ type: 'cyberpunk_racer_car', desc: 'Vehicle physics with acceleration, steering drift friction, and nitro boost.' },
|
|
50
|
+
{ type: 'treasure_chest', desc: 'Interactable StaticBody2D/3D with inventory item dispensing signal.' },
|
|
51
|
+
{ type: 'portal_gate', desc: 'Area2D/3D scene transition gate triggering world map changes.' }
|
|
52
|
+
]
|
|
53
|
+
}, null, 2)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (action === 'spawn_prefab' || action === 'batch_spawn_entities') {
|
|
58
|
+
const typesToSpawn = action === 'batch_spawn_entities' ? ['player_hero_2d', 'skeleton_warrior_ai', 'treasure_chest', 'portal_gate'] : [prefabType];
|
|
59
|
+
const results = [];
|
|
60
|
+
|
|
61
|
+
for (const pt of typesToSpawn) {
|
|
62
|
+
const name = entityName || (pt === 'player_hero_2d' ? 'PlayerHero' : pt === 'skeleton_warrior_ai' ? 'SkeletonWarrior' : pt === 'treasure_chest' ? 'TreasureChest' : 'PortalGate');
|
|
63
|
+
let gdScript = '';
|
|
64
|
+
let tscnContent = '';
|
|
65
|
+
|
|
66
|
+
if (pt === 'player_hero_2d') {
|
|
67
|
+
gdScript = `extends CharacterBody2D
|
|
68
|
+
|
|
69
|
+
@export var speed: float = 160.0
|
|
70
|
+
@export var dash_speed: float = 350.0
|
|
71
|
+
var is_attacking: bool = false
|
|
72
|
+
|
|
73
|
+
@onready var anim: AnimationPlayer = $AnimationPlayer if has_node("AnimationPlayer") else null
|
|
74
|
+
|
|
75
|
+
func _physics_process(delta: float) -> void:
|
|
76
|
+
\tif is_attacking:
|
|
77
|
+
\t\tvelocity = Vector2.ZERO
|
|
78
|
+
\t\tmove_and_slide()
|
|
79
|
+
\t\treturn
|
|
80
|
+
|
|
81
|
+
\tvar dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
|
|
82
|
+
\tvelocity = dir * speed
|
|
83
|
+
\tmove_and_slide()
|
|
84
|
+
|
|
85
|
+
\tif Input.is_action_just_pressed("ui_accept") and not is_attacking:
|
|
86
|
+
\t\tattack()
|
|
87
|
+
\telse:
|
|
88
|
+
\t\tif anim:
|
|
89
|
+
\t\t\tif velocity.length() > 10.0:
|
|
90
|
+
\t\t\t\tanim.play("walk")
|
|
91
|
+
\t\t\telse:
|
|
92
|
+
\t\t\t\tanim.play("idle")
|
|
93
|
+
|
|
94
|
+
func attack() -> void:
|
|
95
|
+
\tis_attacking = true
|
|
96
|
+
\tif anim and anim.has_animation("attack_slash"):
|
|
97
|
+
\t\tanim.play("attack_slash")
|
|
98
|
+
\t\tawait anim.animation_finished
|
|
99
|
+
\tis_attacking = false
|
|
100
|
+
`;
|
|
101
|
+
tscnContent = `[gd_scene load_steps=2 format=3]
|
|
102
|
+
|
|
103
|
+
[ext_resource type="Script" path="res://scripts/actors/${name}.gd" id="1_hero"]
|
|
104
|
+
|
|
105
|
+
[node name="${name}" type="CharacterBody2D" groups=["player"]]
|
|
106
|
+
script = ExtResource("1_hero")
|
|
107
|
+
|
|
108
|
+
[node name="Sprite2D" type="Sprite2D" parent="."]
|
|
109
|
+
|
|
110
|
+
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
|
111
|
+
`;
|
|
112
|
+
} else if (pt === 'skeleton_warrior_ai') {
|
|
113
|
+
gdScript = `extends CharacterBody2D
|
|
114
|
+
# Skeleton Warrior Enemy AI
|
|
115
|
+
@export var max_hp: float = 100.0
|
|
116
|
+
var current_hp: float
|
|
117
|
+
|
|
118
|
+
func _ready() -> void:
|
|
119
|
+
\tcurrent_hp = max_hp
|
|
120
|
+
|
|
121
|
+
func take_damage(amount: float) -> void:
|
|
122
|
+
\tcurrent_hp -= amount
|
|
123
|
+
\tif current_hp <= 0:
|
|
124
|
+
\t\tqueue_free()
|
|
125
|
+
`;
|
|
126
|
+
tscnContent = `[gd_scene load_steps=2 format=3]
|
|
127
|
+
|
|
128
|
+
[ext_resource type="Script" path="res://scripts/actors/${name}.gd" id="1_enemy"]
|
|
129
|
+
|
|
130
|
+
[node name="${name}" type="CharacterBody2D" groups=["enemies"]]
|
|
131
|
+
script = ExtResource("1_enemy")
|
|
132
|
+
`;
|
|
133
|
+
} else if (pt === 'cyberpunk_racer_car') {
|
|
134
|
+
gdScript = `extends CharacterBody3D
|
|
135
|
+
# Cyberpunk Highway Race Car Controller
|
|
136
|
+
@export var acceleration: float = 45.0
|
|
137
|
+
@export var max_speed: float = 180.0
|
|
138
|
+
@export var steering_angle: float = 25.0
|
|
139
|
+
var current_speed: float = 0.0
|
|
140
|
+
|
|
141
|
+
func _physics_process(delta: float) -> void:
|
|
142
|
+
\tvar throttle = Input.get_axis("ui_down", "ui_up")
|
|
143
|
+
\tvar steer = Input.get_axis("ui_right", "ui_left")
|
|
144
|
+
\tcurrent_speed = clamp(current_speed + throttle * acceleration * delta, -30.0, max_speed)
|
|
145
|
+
\trotate_y(deg_to_rad(steer * steering_angle * delta * (current_speed / max_speed)))
|
|
146
|
+
\tvelocity = -transform.basis.z * current_speed
|
|
147
|
+
\tmove_and_slide()
|
|
148
|
+
`;
|
|
149
|
+
tscnContent = `[gd_scene load_steps=2 format=3]
|
|
150
|
+
|
|
151
|
+
[ext_resource type="Script" path="res://scripts/actors/${name}.gd" id="1_car"]
|
|
152
|
+
|
|
153
|
+
[node name="${name}" type="CharacterBody3D" groups=["vehicles"]]
|
|
154
|
+
script = ExtResource("1_car")
|
|
155
|
+
`;
|
|
156
|
+
} else {
|
|
157
|
+
// TreasureChest or PortalGate
|
|
158
|
+
gdScript = `extends Area2D
|
|
159
|
+
signal object_interacted(object_name)
|
|
160
|
+
|
|
161
|
+
func _on_body_entered(body: Node2D) -> void:
|
|
162
|
+
\tif body.is_in_group("player"):
|
|
163
|
+
\t\tprint("[Entity Interaction] Player interacted with ", name)
|
|
164
|
+
\t\temit_signal("object_interacted", name)
|
|
165
|
+
`;
|
|
166
|
+
tscnContent = `[gd_scene load_steps=2 format=3]
|
|
167
|
+
|
|
168
|
+
[ext_resource type="Script" path="res://scripts/actors/${name}.gd" id="1_interact"]
|
|
169
|
+
|
|
170
|
+
[node name="${name}" type="Area2D"]
|
|
171
|
+
script = ExtResource("1_interact")
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
176
|
+
const scriptPath = path.join(projectDir, 'scripts/actors', `${name}.gd`);
|
|
177
|
+
const scenePath = path.join(projectDir, 'scenes/actors', `${name}.tscn`);
|
|
178
|
+
await fs.mkdir(path.dirname(scriptPath), { recursive: true });
|
|
179
|
+
await fs.mkdir(path.dirname(scenePath), { recursive: true });
|
|
180
|
+
await fs.writeFile(scriptPath, gdScript, 'utf8');
|
|
181
|
+
await fs.writeFile(scenePath, tscnContent, 'utf8');
|
|
182
|
+
results.push({ prefab: pt, node_name: name, script: scriptPath, scene: scenePath });
|
|
183
|
+
} else {
|
|
184
|
+
const tempDir = path.join(process.cwd(), '.tmp_causyne_3d/prefabs');
|
|
185
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
186
|
+
await fs.writeFile(path.join(tempDir, `${name}.gd`), gdScript, 'utf8');
|
|
187
|
+
await fs.writeFile(path.join(tempDir, `${name}.tscn`), tscnContent, 'utf8');
|
|
188
|
+
results.push({ prefab: pt, node_name: name, temp_path: tempDir });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
output: JSON.stringify({
|
|
195
|
+
status: 'SPAWNED_PREFABS',
|
|
196
|
+
project_dir: projectDir || 'temp buffer',
|
|
197
|
+
entities_created: results
|
|
198
|
+
}, null, 2)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
203
|
+
} catch (e) {
|
|
204
|
+
return { success: false, error: e.message };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const gameLoreForge = {
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'game.lore_forge',
|
|
8
|
+
description: 'Generates structured worldbuilding lore, faction histories, biome backstories, and character biographies for RPGs and rich game worlds. Can save directly to project `data/world_lore.json` or return formatted JSON.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['generate_world_lore', 'generate_character_bio', 'generate_faction', 'save_lore_to_project'],
|
|
15
|
+
description: 'Action: generate_world_lore (overall setting/history), generate_character_bio (deep NPC/hero backstory), generate_faction (politics/ideology), or save_lore_to_project (persist to JSON).'
|
|
16
|
+
},
|
|
17
|
+
title: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Title of the world, character, or faction (e.g. `Chronicles of Aetheria`, `Archmage Vaelen`, `Shadow Syndicate`).'
|
|
20
|
+
},
|
|
21
|
+
genre: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
enum: ['medieval_fantasy', 'dark_fantasy', 'cyberpunk', 'sci_fi', 'post_apocalyptic', 'steampunk'],
|
|
24
|
+
description: 'Genre and tone of the lore.'
|
|
25
|
+
},
|
|
26
|
+
keyElements: {
|
|
27
|
+
type: 'array',
|
|
28
|
+
items: { type: 'string' },
|
|
29
|
+
description: 'List of key plot devices, relics, biomes, or character traits (e.g. `["The Shadow Relic", "Catacombs", "Betrayed by King"]`).'
|
|
30
|
+
},
|
|
31
|
+
projectDir: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'Optional absolute path to Godot/game project directory to save generated lore.'
|
|
34
|
+
},
|
|
35
|
+
customLoreData: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
description: 'Structured JSON payload to save when action is `save_lore_to_project`.'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
required: ['action', 'title']
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async execute(args) {
|
|
45
|
+
try {
|
|
46
|
+
const { action, title, genre = 'medieval_fantasy', keyElements = [], projectDir, customLoreData } = args;
|
|
47
|
+
|
|
48
|
+
if (action === 'save_lore_to_project') {
|
|
49
|
+
if (!projectDir || !fsSync.existsSync(projectDir)) {
|
|
50
|
+
return { success: false, error: `Valid projectDir is required to save lore. Found: ${projectDir}` };
|
|
51
|
+
}
|
|
52
|
+
const dataDir = path.join(projectDir, 'data');
|
|
53
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
54
|
+
const filePath = path.join(dataDir, `${title.toLowerCase().replace(/[^a-z0-9]+/g, '_')}_lore.json`);
|
|
55
|
+
await fs.writeFile(filePath, JSON.stringify(customLoreData || { title, genre, timestamp: Date.now() }, null, 2), 'utf8');
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
output: JSON.stringify({
|
|
59
|
+
status: 'SAVED_LORE',
|
|
60
|
+
file_path: filePath,
|
|
61
|
+
data_size_bytes: fsSync.existsSync(filePath) ? fsSync.statSync(filePath).size : 0
|
|
62
|
+
}, null, 2)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (action === 'generate_world_lore') {
|
|
67
|
+
const loreTemplate = {
|
|
68
|
+
world_title: title,
|
|
69
|
+
genre: genre,
|
|
70
|
+
creation_myth: `In the dawn of ${title}, the primeval forces shaped the continents from ethereal mist and primordial iron. The balance was broken when the ancient relics shattered across the realms.`,
|
|
71
|
+
core_conflict: keyElements.length > 0 ? `The struggle over ${keyElements.join(', ')} has plunged the kingdoms into perpetual unrest.` : `An ancient darkness stirs beneath the forgotten sanctuaries, threatening to consume all light.`,
|
|
72
|
+
biomes: [
|
|
73
|
+
{ name: 'Whispering Village', atmosphere: 'Misty, ancient oak groves, rustic thatched taverns under constant twilight.' },
|
|
74
|
+
{ name: 'Forgotten Catacombs', atmosphere: 'Subterranean stone labyrinths guarded by skeletal sentinels and shadow wards.' },
|
|
75
|
+
{ name: 'Shadow Sanctuary', atmosphere: 'Obsidian spires pulsing with void energy and arcane glyphs.' }
|
|
76
|
+
],
|
|
77
|
+
historical_eras: [
|
|
78
|
+
{ era: 'Age of Dawn', description: 'The golden peace where mortals and celestials walked together.' },
|
|
79
|
+
{ era: 'The Great Sundering', description: 'The catastrophic war that buried the Shadow Relic deep beneath the earth.' },
|
|
80
|
+
{ era: 'Age of Shadows (Current)', description: 'A tense era of declining magic and rising necromancy.' }
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
85
|
+
const dataDir = path.join(projectDir, 'data');
|
|
86
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
87
|
+
await fs.writeFile(path.join(dataDir, 'world_lore.json'), JSON.stringify(loreTemplate, null, 2), 'utf8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
success: true,
|
|
92
|
+
output: JSON.stringify(loreTemplate, null, 2)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (action === 'generate_character_bio') {
|
|
97
|
+
const bioTemplate = {
|
|
98
|
+
character_name: title,
|
|
99
|
+
genre: genre,
|
|
100
|
+
archetype: keyElements[0] || 'Vengeful Exile',
|
|
101
|
+
background: `${title} was once a revered figure in the high court before being cast out due to the conspiracy surrounding ${keyElements.join(' and ') || 'the forbidden shadow arts'}.`,
|
|
102
|
+
personality_traits: ['Stoic', 'Tactical', 'Haunted by past vows'],
|
|
103
|
+
combat_style: 'Twin-blade high-velocity strikes combined with shadow displacement spells.',
|
|
104
|
+
motivations: {
|
|
105
|
+
primary: 'Reclaim the Shadow Relic and clear their fallen lineage.',
|
|
106
|
+
secret: 'Prevent the awakening of the Void Sovereign locked within their own soul.'
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
111
|
+
const dataDir = path.join(projectDir, 'data');
|
|
112
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
113
|
+
await fs.writeFile(path.join(dataDir, `char_${title.toLowerCase().replace(/[^a-z0-9]+/g, '_')}.json`), JSON.stringify(bioTemplate, null, 2), 'utf8');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
success: true,
|
|
118
|
+
output: JSON.stringify(bioTemplate, null, 2)
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (action === 'generate_faction') {
|
|
123
|
+
const factionTemplate = {
|
|
124
|
+
faction_name: title,
|
|
125
|
+
genre: genre,
|
|
126
|
+
ideology: 'Preservation through absolute control of arcane artifacts.',
|
|
127
|
+
headquarters: 'The Obsidian Citadel of Aetheria',
|
|
128
|
+
key_figures: ['Grand Master Kaelen', 'High Inquisitor Vex'],
|
|
129
|
+
relations: {
|
|
130
|
+
player_hero: 'Suspicious / Hostile until loyalty is proven.',
|
|
131
|
+
shadow_syndicate: 'Mortal enemies locked in clandestine shadow wars.'
|
|
132
|
+
},
|
|
133
|
+
resources: ['Shadow Shards', 'Enchanted Steel', 'Ancient Grimoires']
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
137
|
+
const dataDir = path.join(projectDir, 'data');
|
|
138
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
139
|
+
await fs.writeFile(path.join(dataDir, `faction_${title.toLowerCase().replace(/[^a-z0-9]+/g, '_')}.json`), JSON.stringify(factionTemplate, null, 2), 'utf8');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: true,
|
|
144
|
+
output: JSON.stringify(factionTemplate, null, 2)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
149
|
+
} catch (e) {
|
|
150
|
+
return { success: false, error: e.message };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import fsSync from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export const gameQuestArcBuilder = {
|
|
6
|
+
schema: {
|
|
7
|
+
name: 'game.quest_arc_builder',
|
|
8
|
+
description: 'Designs multi-stage quest arcs (Main Story + Side Quests) with prerequisite chains, objective milestones (`kill_enemies`, `collect_items`, `reach_location`), and balanced XP/gold rewards. Outputs production `quest_database.json` suitable for Godot QuestEngine tracking.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
action: {
|
|
13
|
+
type: 'string',
|
|
14
|
+
enum: ['create_quest_arc', 'add_stage', 'save_quest_db'],
|
|
15
|
+
description: 'Action: create_quest_arc (build main/side quest series), add_stage (append objective), or save_quest_db (persist database to JSON).'
|
|
16
|
+
},
|
|
17
|
+
questId: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Unique quest identifier (`quest_1_shadow_relic`, `side_quest_iron_blades`).'
|
|
20
|
+
},
|
|
21
|
+
questTitle: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Display title of the quest (`The Shadow Relic`, `Forging the Blade`).'
|
|
24
|
+
},
|
|
25
|
+
questType: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
enum: ['main_story', 'side_quest', 'daily', 'boss_bounty'],
|
|
28
|
+
description: 'Category of the quest.'
|
|
29
|
+
},
|
|
30
|
+
stages: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
stage_id: { type: 'string' },
|
|
36
|
+
description: { type: 'string' },
|
|
37
|
+
objective_type: { type: 'string', enum: ['kill_enemies', 'collect_items', 'reach_location', 'talk_npc', 'interact_object'] },
|
|
38
|
+
target_id: { type: 'string' },
|
|
39
|
+
target_count: { type: 'number' }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
description: 'List of quest progression stages.'
|
|
43
|
+
},
|
|
44
|
+
rewards: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
xp: { type: 'number' },
|
|
48
|
+
gold: { type: 'number' },
|
|
49
|
+
items: { type: 'array', items: { type: 'string' } }
|
|
50
|
+
},
|
|
51
|
+
description: 'XP, gold, and item rewards upon completion.'
|
|
52
|
+
},
|
|
53
|
+
projectDir: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Optional absolute path to Godot/game project directory (`res://...`) to save `data/quest_database.json`.'
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
required: ['action', 'questId']
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async execute(args) {
|
|
63
|
+
try {
|
|
64
|
+
const { action, questId, questTitle = 'Quest', questType = 'main_story', stages = [], rewards = { xp: 500, gold: 150, items: ['health_potion'] }, projectDir } = args;
|
|
65
|
+
|
|
66
|
+
if (action === 'create_quest_arc') {
|
|
67
|
+
const defaultStages = stages.length > 0 ? stages : [
|
|
68
|
+
{
|
|
69
|
+
stage_id: 'stage_1_elder_counsel',
|
|
70
|
+
description: 'Speak with Elder Thaddeus in the village square regarding the dark omens.',
|
|
71
|
+
objective_type: 'talk_npc',
|
|
72
|
+
target_id: 'village_elder',
|
|
73
|
+
target_count: 1
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
stage_id: 'stage_2_silver_key',
|
|
77
|
+
description: 'Collect 5 Shadow Shards and trade them to Grom the Blacksmith for the Silver Key.',
|
|
78
|
+
objective_type: 'collect_items',
|
|
79
|
+
target_id: 'shadow_shard',
|
|
80
|
+
target_count: 5
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
stage_id: 'stage_3_catacombs_breach',
|
|
84
|
+
description: 'Pass the ancient wards and enter the Forgotten Catacombs.',
|
|
85
|
+
objective_type: 'reach_location',
|
|
86
|
+
target_id: 'level_2_catacombs_entrance',
|
|
87
|
+
target_count: 1
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
stage_id: 'stage_4_skeleton_purge',
|
|
91
|
+
description: 'Defeat 8 Skeletal Warriors guarding the inner chamber.',
|
|
92
|
+
objective_type: 'kill_enemies',
|
|
93
|
+
target_id: 'skeleton_warrior',
|
|
94
|
+
target_count: 8
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
stage_id: 'stage_5_necromancer_showdown',
|
|
98
|
+
description: 'Vanquish Boss Necromancer Vaelen and claim the Shadow Relic.',
|
|
99
|
+
objective_type: 'kill_enemies',
|
|
100
|
+
target_id: 'boss_necromancer',
|
|
101
|
+
target_count: 1
|
|
102
|
+
}
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const questObject = {
|
|
106
|
+
quest_id: questId,
|
|
107
|
+
title: questTitle,
|
|
108
|
+
type: questType,
|
|
109
|
+
status: 'available',
|
|
110
|
+
prerequisites: [],
|
|
111
|
+
stages: defaultStages,
|
|
112
|
+
rewards: rewards
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (projectDir && fsSync.existsSync(projectDir)) {
|
|
116
|
+
const dataDir = path.join(projectDir, 'data');
|
|
117
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
118
|
+
const dbPath = path.join(dataDir, 'quest_database.json');
|
|
119
|
+
let currentDb = {};
|
|
120
|
+
if (fsSync.existsSync(dbPath)) {
|
|
121
|
+
try { currentDb = JSON.parse(await fs.readFile(dbPath, 'utf8')); } catch (e) {}
|
|
122
|
+
}
|
|
123
|
+
currentDb[questId] = questObject;
|
|
124
|
+
await fs.writeFile(dbPath, JSON.stringify(currentDb, null, 2), 'utf8');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
output: JSON.stringify(questObject, null, 2)
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (action === 'save_quest_db') {
|
|
134
|
+
if (!projectDir || !fsSync.existsSync(projectDir)) {
|
|
135
|
+
return { success: false, error: `projectDir is required to save quest database.` };
|
|
136
|
+
}
|
|
137
|
+
const dataDir = path.join(projectDir, 'data');
|
|
138
|
+
await fs.mkdir(dataDir, { recursive: true });
|
|
139
|
+
const dbPath = path.join(dataDir, 'quest_database.json');
|
|
140
|
+
await fs.writeFile(dbPath, JSON.stringify({ [questId]: { quest_id: questId, title: questTitle, stages, rewards } }, null, 2), 'utf8');
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
output: JSON.stringify({ status: 'SAVED_QUEST_DB', path: dbPath }, null, 2)
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return { success: false, error: e.message };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|