use crate::ecs::world::World;
use crate::mcp::McpResponse;
pub(crate) fn process_batch_operations(
world: &mut World,
operations: Vec<crate::mcp::BatchOperation>,
) -> Vec<McpResponse> {
let mut results = Vec::with_capacity(operations.len());
for operation in operations {
let response = match operation.operation.to_lowercase().as_str() {
"spawn_entity" => {
let (Some(name), Some(mesh)) = (operation.name, operation.mesh) else {
results.push(McpResponse::Error(
"spawn_entity requires 'name' and 'mesh' fields".to_string(),
));
continue;
};
let position = operation.position.unwrap_or([0.0, 0.0, 0.0]);
super::entity::mcp_spawn_entity(
world,
crate::mcp::SpawnEntityRequest {
name,
mesh,
position,
scale: operation.scale,
color: operation.color,
emissive: operation.emissive,
parent: operation.parent,
alpha: operation.alpha,
},
)
}
"despawn_entity" => {
let Some(name) = operation.name else {
results.push(McpResponse::Error(
"despawn_entity requires 'name' field".to_string(),
));
continue;
};
super::entity::mcp_despawn_entity(world, name)
}
"set_position" => {
let (Some(name), Some(position)) = (operation.name, operation.position) else {
results.push(McpResponse::Error(
"set_position requires 'name' and 'position' fields".to_string(),
));
continue;
};
super::transform::mcp_set_position(
world,
crate::mcp::SetPositionRequest { name, position },
)
.unwrap_or_else(|error| error)
}
"set_rotation" => {
let (Some(name), Some(rotation)) = (operation.name, operation.rotation) else {
results.push(McpResponse::Error(
"set_rotation requires 'name' and 'rotation' fields".to_string(),
));
continue;
};
super::transform::mcp_set_rotation(
world,
crate::mcp::SetRotationRequest { name, rotation },
)
.unwrap_or_else(|error| error)
}
"set_scale" => {
let (Some(name), Some(scale)) = (operation.name, operation.scale) else {
results.push(McpResponse::Error(
"set_scale requires 'name' and 'scale' fields".to_string(),
));
continue;
};
super::transform::mcp_set_scale(world, crate::mcp::SetScaleRequest { name, scale })
.unwrap_or_else(|error| error)
}
"set_material_color" => {
let (Some(name), Some(color)) = (operation.name, operation.color) else {
results.push(McpResponse::Error(
"set_material_color requires 'name' and 'color' fields".to_string(),
));
continue;
};
super::material::mcp_set_material_color(
world,
crate::mcp::SetMaterialColorRequest { name, color },
)
.unwrap_or_else(|error| error)
}
"set_emissive" => {
let (Some(name), Some(emissive)) = (operation.name, operation.emissive) else {
results.push(McpResponse::Error(
"set_emissive requires 'name' and 'emissive' fields".to_string(),
));
continue;
};
super::material::mcp_set_emissive(
world,
crate::mcp::SetEmissiveRequest { name, emissive },
)
.unwrap_or_else(|error| error)
}
"set_parent" => {
let Some(name) = operation.name else {
results.push(McpResponse::Error(
"set_parent requires 'name' field".to_string(),
));
continue;
};
super::transform::mcp_set_parent(
world,
crate::mcp::SetParentRequest {
name,
parent: operation.parent,
},
)
.unwrap_or_else(|error| error)
}
"clear_scene" => super::entity::mcp_clear_scene(world),
"list_entities" => {
let names: Vec<String> = world.resources.entity_names.keys().cloned().collect();
McpResponse::EntityList(names)
}
"query_entity" => {
let Some(name) = operation.name else {
results.push(McpResponse::Error(
"query_entity requires 'name' field".to_string(),
));
continue;
};
super::entity::mcp_query_entity(world, crate::mcp::QueryEntityRequest { name })
.unwrap_or_else(|error| error)
}
"set_atmosphere" => {
let Some(atmosphere) = operation.atmosphere else {
results.push(McpResponse::Error(
"set_atmosphere requires 'atmosphere' field".to_string(),
));
continue;
};
super::environment::mcp_set_atmosphere(
world,
crate::mcp::SetAtmosphereRequest { atmosphere },
)
}
_ => McpResponse::Error(format!(
"Unknown operation '{}'. Valid operations: spawn_entity, despawn_entity, set_position, set_rotation, set_scale, set_material_color, set_emissive, set_parent, query_entity, list_entities, clear_scene, set_atmosphere",
operation.operation
)),
};
results.push(response);
}
results
}
fn parse_vec3(s: &str) -> Option<[f32; 3]> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() == 1 {
let v = parts[0].parse().ok()?;
Some([v, v, v])
} else if parts.len() == 3 {
Some([
parts[0].parse().ok()?,
parts[1].parse().ok()?,
parts[2].parse().ok()?,
])
} else {
None
}
}
fn parse_vec4(s: &str) -> Option<[f32; 4]> {
let parts: Vec<&str> = s.split(',').collect();
if parts.len() == 4 {
Some([
parts[0].parse().ok()?,
parts[1].parse().ok()?,
parts[2].parse().ok()?,
parts[3].parse().ok()?,
])
} else {
None
}
}
pub(crate) fn process_text_commands(world: &mut World, commands: &str) -> Vec<McpResponse> {
let mut results = Vec::new();
for line in commands.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue;
}
let response = match parts[0].to_lowercase().as_str() {
"spawn" | "s" => {
if parts.len() < 4 {
McpResponse::Error(
"spawn requires: spawn <name> <mesh> <x>,<y>,<z> [options]".to_string(),
)
} else {
let name = parts[1].to_string();
let mesh = parts[2].to_string();
let Some(position) = parse_vec3(parts[3]) else {
results.push(McpResponse::Error(format!(
"Invalid position '{}'. Use x,y,z format",
parts[3]
)));
continue;
};
let mut scale = [1.0, 1.0, 1.0];
let mut color = None;
let mut emissive = None;
let mut parent = None;
let mut alpha = None;
for part in &parts[4..] {
if let Some(val) = part
.strip_prefix("scale:")
.or_else(|| part.strip_prefix("sc:"))
.or_else(|| part.strip_prefix("s:"))
{
if let Some(s) = parse_vec3(val) {
scale = s;
}
} else if let Some(val) = part
.strip_prefix("color:")
.or_else(|| part.strip_prefix("c:"))
{
color = parse_vec4(val);
} else if let Some(val) = part
.strip_prefix("emissive:")
.or_else(|| part.strip_prefix("e:"))
{
emissive = parse_vec3(val);
} else if let Some(val) = part
.strip_prefix("parent:")
.or_else(|| part.strip_prefix("p:"))
{
parent = Some(val.to_string());
} else if let Some(val) = part
.strip_prefix("alpha:")
.or_else(|| part.strip_prefix("a:"))
{
alpha = Some(val.to_string());
}
}
super::entity::mcp_spawn_entity(
world,
crate::mcp::SpawnEntityRequest {
name,
mesh,
position,
scale: Some(scale),
color,
emissive,
parent,
alpha,
},
)
}
}
"despawn" | "d" => {
if parts.len() < 2 {
McpResponse::Error("despawn requires: despawn <name>".to_string())
} else {
super::entity::mcp_despawn_entity(world, parts[1].to_string())
}
}
"pos" | "position" => {
if parts.len() < 3 {
McpResponse::Error("pos requires: pos <name> <x>,<y>,<z>".to_string())
} else {
let Some(position) = parse_vec3(parts[2]) else {
results.push(McpResponse::Error(format!(
"Invalid position '{}'",
parts[2]
)));
continue;
};
super::transform::mcp_set_position(
world,
crate::mcp::SetPositionRequest {
name: parts[1].to_string(),
position,
},
)
.unwrap_or_else(|error| error)
}
}
"rot" | "rotation" => {
if parts.len() < 3 {
McpResponse::Error("rot requires: rot <name> <pitch>,<yaw>,<roll>".to_string())
} else {
let Some(rotation) = parse_vec3(parts[2]) else {
results.push(McpResponse::Error(format!(
"Invalid rotation '{}'",
parts[2]
)));
continue;
};
super::transform::mcp_set_rotation(
world,
crate::mcp::SetRotationRequest {
name: parts[1].to_string(),
rotation,
},
)
.unwrap_or_else(|error| error)
}
}
"scale" | "sc" => {
if parts.len() < 3 {
McpResponse::Error("scale requires: scale <name> <x>,<y>,<z>".to_string())
} else {
let Some(scale) = parse_vec3(parts[2]) else {
results.push(McpResponse::Error(format!("Invalid scale '{}'", parts[2])));
continue;
};
super::transform::mcp_set_scale(
world,
crate::mcp::SetScaleRequest {
name: parts[1].to_string(),
scale,
},
)
.unwrap_or_else(|error| error)
}
}
"color" | "c" => {
if parts.len() < 3 {
McpResponse::Error("color requires: color <name> <r>,<g>,<b>,<a>".to_string())
} else {
let Some(color) = parse_vec4(parts[2]) else {
results.push(McpResponse::Error(format!("Invalid color '{}'", parts[2])));
continue;
};
super::material::mcp_set_material_color(
world,
crate::mcp::SetMaterialColorRequest {
name: parts[1].to_string(),
color,
},
)
.unwrap_or_else(|error| error)
}
}
"emissive" | "e" => {
if parts.len() < 3 {
McpResponse::Error("emissive requires: emissive <name> <r>,<g>,<b>".to_string())
} else {
let Some(emissive) = parse_vec3(parts[2]) else {
results.push(McpResponse::Error(format!(
"Invalid emissive '{}'",
parts[2]
)));
continue;
};
super::material::mcp_set_emissive(
world,
crate::mcp::SetEmissiveRequest {
name: parts[1].to_string(),
emissive,
},
)
.unwrap_or_else(|error| error)
}
}
"parent" | "p" => {
if parts.len() < 3 {
McpResponse::Error("parent requires: parent <child> <parent>".to_string())
} else {
super::transform::mcp_set_parent(
world,
crate::mcp::SetParentRequest {
name: parts[1].to_string(),
parent: Some(parts[2].to_string()),
},
)
.unwrap_or_else(|error| error)
}
}
"unparent" => {
if parts.len() < 2 {
McpResponse::Error("unparent requires: unparent <name>".to_string())
} else {
super::transform::mcp_set_parent(
world,
crate::mcp::SetParentRequest {
name: parts[1].to_string(),
parent: None,
},
)
.unwrap_or_else(|error| error)
}
}
"clear" | "clr" => super::entity::mcp_clear_scene(world),
"list" | "ls" => {
let names: Vec<String> = world.resources.entity_names.keys().cloned().collect();
McpResponse::EntityList(names)
}
"query" | "q" => {
if parts.len() < 2 {
McpResponse::Error("query requires: query <name>".to_string())
} else {
super::entity::mcp_query_entity(
world,
crate::mcp::QueryEntityRequest {
name: parts[1].to_string(),
},
)
.unwrap_or_else(|error| error)
}
}
"atmosphere" | "atmo" => {
if parts.len() < 2 {
McpResponse::Error("atmosphere requires: atmosphere <type>".to_string())
} else {
super::environment::mcp_set_atmosphere(
world,
crate::mcp::SetAtmosphereRequest {
atmosphere: parts[1].to_string(),
},
)
}
}
"hdr" => {
if parts.len() < 2 {
McpResponse::Error("hdr requires: hdr <path>".to_string())
} else {
super::environment::mcp_load_hdr(
world,
crate::mcp::LoadHdrRequest {
path: parts[1].to_string(),
},
)
}
}
_ => McpResponse::Error(format!(
"Unknown command '{}'. Valid: s(pawn), d(espawn), pos, rot, sc(ale), c(olor), e(missive), p(arent), b(ehavior), clr/clear, ls/list, q(uery), atmo(sphere), hdr",
parts[0]
)),
};
results.push(response);
}
results
}