nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use crate::ecs::world::World;
use crate::mcp::McpResponse;

fn spawn_mesh_by_type(world: &mut World, mesh_type: &str) -> Option<freecs::Entity> {
    match mesh_type.to_lowercase().as_str() {
        "cube" => Some(crate::ecs::world::spawn_cube_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        "sphere" => Some(crate::ecs::world::spawn_sphere_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        "cylinder" => Some(crate::ecs::world::spawn_cylinder_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        "cone" => Some(crate::ecs::world::spawn_cone_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        "plane" => Some(crate::ecs::world::spawn_plane_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        "torus" => Some(crate::ecs::world::spawn_torus_at(
            world,
            nalgebra_glm::Vec3::zeros(),
        )),
        _ => None,
    }
}

pub(crate) fn mcp_spawn_entity(
    world: &mut World,
    request: crate::mcp::SpawnEntityRequest,
) -> McpResponse {
    let crate::mcp::SpawnEntityRequest {
        name,
        mesh,
        position,
        scale,
        color,
        emissive,
        parent: parent_name,
        alpha: alpha_mode,
    } = request;
    let scale = scale.unwrap_or([1.0, 1.0, 1.0]);
    use crate::ecs::transform::components::Parent;
    use crate::ecs::world::{GLOBAL_TRANSFORM, LOCAL_TRANSFORM, NAME, PARENT};

    if world.resources.entity_names.contains_key(&name) {
        return McpResponse::Error(format!("Entity '{}' already exists", name));
    }

    let parent_entity = if let Some(ref pname) = parent_name {
        let Some(&parent) = world.resources.entity_names.get(pname) else {
            return McpResponse::Error(format!("Parent entity '{}' not found", pname));
        };
        Some(parent)
    } else {
        None
    };

    let pivot = world.spawn_entities(LOCAL_TRANSFORM | GLOBAL_TRANSFORM | PARENT, 1)[0];
    if let Some(transform) = world.get_local_transform_mut(pivot) {
        transform.translation = nalgebra_glm::Vec3::new(position[0], position[1], position[2]);
    }
    crate::ecs::transform::commands::mark_local_transform_dirty(world, pivot);

    if let Some(parent) = parent_entity {
        world.update_parent(pivot, Some(Parent(Some(parent))));
    }

    let Some(entity) = spawn_mesh_by_type(world, &mesh) else {
        crate::ecs::world::commands::despawn_recursive_immediate(world, pivot);
        return McpResponse::Error(format!(
            "Unknown mesh type '{}'. Valid types: Cube, Sphere, Cylinder, Cone, Plane, Torus",
            mesh
        ));
    };

    world.update_parent(entity, Some(Parent(Some(pivot))));

    if scale != [1.0, 1.0, 1.0] {
        if let Some(transform) = world.get_local_transform_mut(pivot) {
            transform.scale = nalgebra_glm::Vec3::new(scale[0], scale[1], scale[2]);
        }
        crate::ecs::transform::commands::mark_local_transform_dirty(world, pivot);
    }

    let material_name = name.clone();
    let mut material = crate::ecs::material::components::Material::default();
    if let Some(c) = color {
        material.base_color = c;
    }
    if let Some(e) = emissive {
        material.emissive_factor = e;
    }
    if let Some(ref alpha) = alpha_mode {
        material.alpha_mode = match alpha.to_lowercase().as_str() {
            "opaque" => crate::ecs::material::components::AlphaMode::Opaque,
            "mask" => crate::ecs::material::components::AlphaMode::Mask,
            "blend" => crate::ecs::material::components::AlphaMode::Blend,
            _ => crate::ecs::material::components::AlphaMode::Opaque,
        };
    }
    crate::ecs::material::resources::material_registry_insert(
        &mut world.resources.material_registry,
        material_name.clone(),
        material,
    );
    if let Some(&index) = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get(&material_name)
    {
        world
            .resources
            .material_registry
            .registry
            .add_reference(index);
    }
    world.set_material_ref(
        entity,
        crate::ecs::material::components::MaterialRef::new(material_name),
    );

    world.add_components(pivot, NAME);
    world.set_name(pivot, crate::ecs::name::components::Name(name.clone()));
    world.resources.entity_names.insert(name.clone(), pivot);

    let parent_info = parent_name
        .map(|p| format!(" (child of '{}')", p))
        .unwrap_or_default();
    McpResponse::Success(format!(
        "Spawned entity '{}' with {} mesh at {:?}{}",
        name, mesh, position, parent_info
    ))
}

pub(crate) fn mcp_despawn_entity(world: &mut World, name: String) -> McpResponse {
    if let Some(entity) = world.resources.entity_names.remove(&name) {
        crate::ecs::world::commands::despawn_recursive_immediate(world, entity);
        McpResponse::Success(format!("Despawned entity '{}'", name))
    } else {
        McpResponse::Error(format!("Entity '{}' not found", name))
    }
}

pub(crate) fn mcp_query_entity(
    world: &World,
    request: crate::mcp::QueryEntityRequest,
) -> Result<McpResponse, McpResponse> {
    let entity = super::resolve_entity(world, &request.name)?;

    let Some(transform) = world.get_local_transform(entity) else {
        return Err(McpResponse::Error(format!(
            "Entity '{}' has no transform",
            request.name
        )));
    };

    Ok(McpResponse::EntityInfo {
        name: request.name,
        position: [
            transform.translation.x,
            transform.translation.y,
            transform.translation.z,
        ],
        rotation: [
            transform.rotation.i,
            transform.rotation.j,
            transform.rotation.k,
            transform.rotation.w,
        ],
        scale: [transform.scale.x, transform.scale.y, transform.scale.z],
    })
}

pub(crate) fn mcp_clear_scene(world: &mut World) -> McpResponse {
    let entities_to_despawn: Vec<_> = world.resources.entity_names.values().copied().collect();
    let count = entities_to_despawn.len();
    for entity in entities_to_despawn {
        crate::ecs::world::commands::despawn_recursive_immediate(world, entity);
    }
    world.resources.entity_names.clear();
    McpResponse::Success(format!("Cleared {} entities from scene", count))
}

pub(crate) fn mcp_set_visibility(
    world: &mut World,
    request: crate::mcp::SetVisibilityRequest,
) -> Result<McpResponse, McpResponse> {
    use crate::ecs::visibility::components::Visibility;

    let entity = super::resolve_entity(world, &request.name)?;

    world.set_visibility(
        entity,
        Visibility {
            visible: request.visible,
        },
    );
    Ok(McpResponse::Success(format!(
        "Visibility set to {} for '{}'",
        request.visible, request.name
    )))
}