Files
tactical-shooter/package/dist/tools/scene-tools.js
T
shawn 91b878f347 Quaternius weapons, input fixes, sound & path fixes
- Added 6 Quaternius weapon models (Pistol, Revolver, Rifle, Shotgun, P90, SniperRifle)
- Created weapon resource files with balanced stats
- Wired Pistol + Rifle into player.tscn replacing Kenney blasters
- Fixed WASD input (matched input map action names)
- Fixed Escape key toggles mouse capture
- Added Audio autoload singleton
- Fixed broken sound asset paths across all scripts
- Fixed all scene ext_resource text paths (nested under assets/kenney-fps/)
- Fixed all .import source_file paths
- Deleted stale .import files (will be regenerated by editor on open)
2026-07-05 22:53:27 -04:00

342 lines
17 KiB
JavaScript

import { join } from 'path';
import { existsSync } from 'fs';
import { normalizeParameters, validateSubPath, validateNodePath, createErrorResponse, validateProjectArgs, validateSceneArgs, } from '../utils/godot-runner.js';
import { executeSceneOp } from '../utils/handler-helpers.js';
export const sceneToolDefinitions = [
{
name: 'create_scene',
description: 'Create a new Godot scene file with a single root node. Writes a fresh .tscn at scenePath. Use when starting a new scene from scratch; for adding nodes to an existing scene, use add_node. rootNodeType defaults to Node2D — pass "Node3D" for 3D scenes or "Control" for UI. Saves automatically. Overwrites silently if the file already exists. Returns: success and the scenePath that was written.',
annotations: { idempotentHint: true },
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
scenePath: {
type: 'string',
description: 'Scene file path relative to the project (e.g. "scenes/main.tscn")',
},
rootNodeType: { type: 'string', description: 'Root node type (default: Node2D)' },
},
required: ['projectPath', 'scenePath'],
},
outputSchema: {
type: 'object',
properties: {
success: { type: 'boolean' },
scenePath: { type: 'string' },
},
},
},
{
name: 'add_node',
description: 'Add a node to a Godot scene. Saves automatically. Common spatial properties (position, position3d, rotation, scale, visible, modulate) can be set as top-level params; for any other property, pass it under properties. Vector2/Vector3/Color values auto-convert from {x,y}/{x,y,z}/{r,g,b,a}. parentNodePath defaults to the scene root. Returns a plain-text confirmation message naming the new node and type. Errors if nodeType is not a registered Godot class or parentNodePath does not exist.',
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
scenePath: { type: 'string', description: 'Scene file path relative to the project' },
nodeType: {
type: 'string',
description: 'Godot node class to instantiate (e.g. "Sprite2D", "CollisionShape2D", "Label")',
},
nodeName: {
type: 'string',
description: 'Name for the new node as it appears in the scene tree',
},
parentNodePath: {
type: 'string',
description: 'Parent node path from scene root (e.g. "root/Player"). Defaults to the root node.',
},
position: {
type: 'object',
description: 'Vector2 position (e.g. {"x": 100, "y": 200})',
properties: { x: { type: 'number' }, y: { type: 'number' } },
},
position3d: {
type: 'object',
description: 'Vector3 position for 3D nodes (e.g. {"x": 0, "y": 1, "z": 0})',
properties: { x: { type: 'number' }, y: { type: 'number' }, z: { type: 'number' } },
},
rotation: { type: 'number', description: 'Rotation in radians' },
scale: {
type: 'object',
description: 'Vector2 scale (e.g. {"x": 2, "y": 2})',
properties: { x: { type: 'number' }, y: { type: 'number' } },
},
visible: { type: 'boolean', description: 'Whether the node is visible' },
modulate: {
type: 'object',
description: 'Color modulation (e.g. {"r": 1, "g": 0, "b": 0, "a": 1})',
properties: {
r: { type: 'number' },
g: { type: 'number' },
b: { type: 'number' },
a: { type: 'number' },
},
},
properties: {
type: 'object',
description: 'Additional property values as a JSON object. Top-level params (position, rotation, etc.) take precedence over keys in this dict.',
},
},
required: ['projectPath', 'scenePath', 'nodeType', 'nodeName'],
},
},
{
name: 'load_sprite',
description: 'Set the texture on an existing Sprite2D, Sprite3D, or TextureRect node. Use this when the node already exists; for new nodes, pass texture via add_node properties. Saves automatically. texturePath must be a real file under projectPath. Returns a plain-text confirmation message naming the loaded texture. Errors if the node is not one of those three classes, or the texture file does not exist.',
annotations: { idempotentHint: true },
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
scenePath: { type: 'string', description: 'Scene file path relative to the project' },
nodePath: {
type: 'string',
description: 'Path to the target node from scene root (e.g. "root/Player/Sprite2D")',
},
texturePath: {
type: 'string',
description: 'Path to the texture file relative to the project (e.g. "assets/player.png")',
},
},
required: ['projectPath', 'scenePath', 'nodePath', 'texturePath'],
},
},
{
name: 'save_scene',
description: 'Re-pack and save a scene, optionally to a different path (save-as). Most mutations (add_node, set_node_properties, delete_nodes, etc.) auto-save — only use this for save-as via newPath, or to re-canonicalize a hand-edited .tscn. Overwrites silently. Returns a plain-text confirmation naming the save path. Errors if the scene file does not exist.',
annotations: { idempotentHint: true },
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
scenePath: { type: 'string', description: 'Scene file path relative to the project' },
newPath: {
type: 'string',
description: 'Save to a different path (relative to project) instead of overwriting the original',
},
},
required: ['projectPath', 'scenePath'],
},
},
{
name: 'export_mesh_library',
description: 'Export a scene of MeshInstance3D nodes as a MeshLibrary .res file for use in GridMap. Use this when authoring tile palettes for grid-based 3D levels; ignore for 2D or general scene work. The source scene must contain MeshInstance3D children. Pass meshItemNames to export a subset, or omit to export all. Saves the .res to outputPath, overwriting silently. Returns a plain-text confirmation with the exported item count. Errors if the scene contains no valid meshes.',
annotations: { destructiveHint: true },
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
scenePath: { type: 'string', description: 'Scene file path relative to the project' },
outputPath: {
type: 'string',
description: 'Output path for the MeshLibrary .res file (relative to project)',
},
meshItemNames: {
type: 'array',
items: { type: 'string' },
description: 'Names of specific mesh items to export. Omit to export all.',
},
},
required: ['projectPath', 'scenePath', 'outputPath'],
},
},
{
name: 'batch_scene_operations',
description: 'Use this instead of chaining add_node / load_sprite / save_scene calls when you have multiple mutations on the same or related scenes — runs in one Godot process (~3s startup avoided per call) and shares an in-memory scene cache, saving once at the end. Each item picks its sub-operation (add_node, load_sprite, save) and supplies its own params; abortOnError stops on first failure (default false continues). Returns: results[] in input order, each tagged with operation and scenePath plus success or error.',
annotations: { destructiveHint: true },
inputSchema: {
type: 'object',
properties: {
projectPath: { type: 'string', description: 'Path to the Godot project directory' },
operations: {
type: 'array',
description: 'Ordered list of scene operations. Each item has its own operation and scenePath.',
items: {
type: 'object',
properties: {
operation: {
type: 'string',
enum: ['add_node', 'load_sprite', 'save'],
description: 'The sub-operation to perform',
},
scenePath: { type: 'string', description: 'Scene file path for this operation' },
nodeType: { type: 'string', description: '[add_node] Node class to instantiate' },
nodeName: { type: 'string', description: '[add_node] Name for the new node' },
parentNodePath: {
type: 'string',
description: '[add_node] Parent node path (defaults to root)',
},
properties: { type: 'object', description: '[add_node] Initial property values' },
nodePath: { type: 'string', description: '[load_sprite] Target node path' },
texturePath: {
type: 'string',
description: '[load_sprite] Texture file path relative to project',
},
newPath: {
type: 'string',
description: '[save] Save to a different path instead of overwriting',
},
},
required: ['operation'],
},
},
abortOnError: {
type: 'boolean',
description: 'Stop processing on first error (default: false)',
},
},
required: ['projectPath', 'operations'],
},
outputSchema: {
type: 'object',
properties: {
results: {
type: 'array',
items: {
type: 'object',
properties: {
operation: { type: 'string' },
scenePath: { type: 'string' },
success: { type: 'boolean' },
error: { type: 'string' },
},
},
},
},
},
},
];
// --- Handlers ---
export async function handleCreateScene(runner, args) {
args = normalizeParameters(args);
const v = validateSceneArgs(args, { sceneRequired: false });
if ('isError' in v)
return v;
const params = {
scenePath: args.scenePath,
rootNodeType: args.rootNodeType || 'Node2D',
};
return executeSceneOp(runner, 'create_scene', params, v.projectPath, 'Failed to create scene', ['Check if the root node type is valid'], undefined, { parseStdoutAsJson: true });
}
export async function handleAddNode(runner, args) {
args = normalizeParameters(args);
const v = validateSceneArgs(args);
if ('isError' in v)
return v;
if (!args.nodeType || !args.nodeName) {
return createErrorResponse('nodeType and nodeName are required', [
'Provide both nodeType and nodeName',
]);
}
// Merge promoted top-level params into properties dict
const promotedKeys = [
'position',
'position3d',
'rotation',
'scale',
'visible',
'modulate',
];
const mergedProps = args.properties || {};
for (const key of promotedKeys) {
if (args[key] !== undefined) {
mergedProps[key] = args[key];
}
}
const params = {
scenePath: args.scenePath,
nodeType: args.nodeType,
nodeName: args.nodeName,
};
if (args.parentNodePath)
params.parentNodePath = args.parentNodePath;
if (Object.keys(mergedProps).length > 0)
params.properties = mergedProps;
return executeSceneOp(runner, 'add_node', params, v.projectPath, 'Failed to add node', [
'Check if the node type is valid',
'Ensure the parent node path exists',
]);
}
export async function handleLoadSprite(runner, args) {
args = normalizeParameters(args);
const v = validateSceneArgs(args);
if ('isError' in v)
return v;
if (!args.nodePath || !validateNodePath(args.nodePath)) {
return createErrorResponse('Valid nodePath is required', ['Provide the target node path']);
}
if (!args.texturePath || !validateSubPath(v.projectPath, args.texturePath)) {
return createErrorResponse('Valid texturePath is required', [
'Provide a relative texture path that stays inside the project directory',
]);
}
const textureFullPath = join(v.projectPath, args.texturePath);
if (!existsSync(textureFullPath)) {
return createErrorResponse(`Texture file does not exist: ${args.texturePath}`, [
'Ensure the texture path is correct',
]);
}
const params = {
scenePath: args.scenePath,
nodePath: args.nodePath,
texturePath: args.texturePath,
};
return executeSceneOp(runner, 'load_sprite', params, v.projectPath, 'Failed to load sprite', [
'Check if the node is a Sprite2D, Sprite3D, or TextureRect',
]);
}
export async function handleSaveScene(runner, args) {
args = normalizeParameters(args);
const v = validateSceneArgs(args);
if ('isError' in v)
return v;
if (args.newPath && !validateSubPath(v.projectPath, args.newPath)) {
return createErrorResponse('Invalid newPath', [
'Provide a valid relative path without ".." that stays inside the project directory',
]);
}
const params = { scenePath: args.scenePath };
if (args.newPath)
params.newPath = args.newPath;
return executeSceneOp(runner, 'save_scene', params, v.projectPath, 'Failed to save scene', [
'Check if the scene file is valid',
]);
}
export async function handleExportMeshLibrary(runner, args) {
args = normalizeParameters(args);
const v = validateSceneArgs(args);
if ('isError' in v)
return v;
if (!args.outputPath || !validateSubPath(v.projectPath, args.outputPath)) {
return createErrorResponse('Valid outputPath is required', [
'Provide an output path for the .res file that stays inside the project directory',
]);
}
const params = {
scenePath: args.scenePath,
outputPath: args.outputPath,
};
if (args.meshItemNames && Array.isArray(args.meshItemNames)) {
params.meshItemNames = args.meshItemNames;
}
return executeSceneOp(runner, 'export_mesh_library', params, v.projectPath, 'Failed to export mesh library', ['Check if the scene contains valid 3D meshes']);
}
export async function handleBatchSceneOperations(runner, args) {
args = normalizeParameters(args);
const v = validateProjectArgs(args);
if ('isError' in v)
return v;
if (!args.operations || !Array.isArray(args.operations)) {
return createErrorResponse('operations array is required', [
'Provide an operations array with at least one item',
]);
}
const params = {
operations: args.operations,
abortOnError: args.abortOnError ?? false,
};
return executeSceneOp(runner, 'batch_scene_operations', params, v.projectPath, 'Batch scene operations failed', ['Check that all scene paths exist', 'Ensure node types are valid'], undefined, { parseStdoutAsJson: true });
}
//# sourceMappingURL=scene-tools.js.map