nightshade 0.13.3

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

fn find_mesh_child(world: &World, pivot: freecs::Entity) -> Option<freecs::Entity> {
    use crate::ecs::world::{MATERIAL_REF, PARENT};

    if world.core.entity_has_components(pivot, MATERIAL_REF) {
        return Some(pivot);
    }

    world
        .core
        .query_entities(PARENT | MATERIAL_REF)
        .find(|&child| {
            world
                .core
                .get_parent(child)
                .map(|p| p.0 == Some(pivot))
                .unwrap_or(false)
        })
}

fn ensure_unique_material_name(
    world: &mut World,
    pivot: freecs::Entity,
    entity_name: &str,
) -> Result<String, McpResponse> {
    use crate::ecs::generational_registry::registry_entry_by_name;

    let Some(mesh) = find_mesh_child(world, pivot) else {
        return Err(McpResponse::Error(format!(
            "Entity '{}' has no mesh with material",
            entity_name
        )));
    };

    let Some(material_ref) = world.core.get_material_ref(mesh) else {
        return Err(McpResponse::Error(format!(
            "Entity '{}' has no material reference",
            entity_name
        )));
    };

    let current_material_name = material_ref.name.clone();
    let ref_count = world
        .resources
        .material_registry
        .registry
        .name_to_index
        .get(&current_material_name)
        .map(|&index| {
            world
                .resources
                .material_registry
                .registry
                .reference_count(index)
        })
        .unwrap_or(0);

    if ref_count > 1 {
        let unique_name = entity_name.to_string();
        if !world
            .resources
            .material_registry
            .registry
            .name_to_index
            .contains_key(&unique_name)
        {
            let new_material = registry_entry_by_name(
                &world.resources.material_registry.registry,
                &current_material_name,
            )
            .cloned()
            .unwrap_or_default();
            crate::ecs::material::resources::material_registry_insert(
                &mut world.resources.material_registry,
                unique_name.clone(),
                new_material,
            );
        }
        if let Some(&old_index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&current_material_name)
        {
            world
                .resources
                .material_registry
                .registry
                .remove_reference(old_index);
        }
        if let Some(&new_index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&unique_name)
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(new_index);
        }
        world.core.set_material_ref(
            mesh,
            crate::ecs::material::components::MaterialRef::new(unique_name.clone()),
        );
        Ok(unique_name)
    } else {
        Ok(current_material_name)
    }
}

pub(crate) fn mcp_set_material_color(
    world: &mut World,
    request: crate::mcp::SetMaterialColorRequest,
) -> Result<McpResponse, McpResponse> {
    use crate::ecs::generational_registry::registry_entry_by_name_mut;

    let pivot = super::resolve_entity(world, &request.name)?;
    let target_material_name = ensure_unique_material_name(world, pivot, &request.name)?;

    let Some(material) = registry_entry_by_name_mut(
        &mut world.resources.material_registry.registry,
        &target_material_name,
    ) else {
        return Err(McpResponse::Error(format!(
            "Material '{}' not found for entity '{}'",
            target_material_name, request.name
        )));
    };

    material.base_color = [
        request.color[0],
        request.color[1],
        request.color[2],
        request.color[3],
    ];
    Ok(McpResponse::Success(format!(
        "Set material color of '{}' to {:?}",
        request.name, request.color
    )))
}

pub(crate) fn mcp_set_emissive(
    world: &mut World,
    request: crate::mcp::SetEmissiveRequest,
) -> Result<McpResponse, McpResponse> {
    use crate::ecs::generational_registry::registry_entry_by_name_mut;

    let pivot = super::resolve_entity(world, &request.name)?;
    let target_material_name = ensure_unique_material_name(world, pivot, &request.name)?;

    let Some(material) = registry_entry_by_name_mut(
        &mut world.resources.material_registry.registry,
        &target_material_name,
    ) else {
        return Err(McpResponse::Error(format!(
            "Material '{}' not found for entity '{}'",
            target_material_name, request.name
        )));
    };

    material.emissive_factor = request.emissive;
    Ok(McpResponse::Success(format!(
        "Set emissive of '{}' to {:?}",
        request.name, request.emissive
    )))
}

pub(crate) fn mcp_set_material(
    world: &mut World,
    request: crate::mcp::SetMaterialRequest,
) -> Result<McpResponse, McpResponse> {
    let crate::mcp::SetMaterialRequest {
        name,
        base_color,
        emissive,
        roughness,
        metallic,
        alpha_mode,
        alpha_cutoff,
    } = request;
    use crate::ecs::generational_registry::registry_entry_by_name_mut;
    use crate::ecs::material::components::AlphaMode;

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

    let Some(mat_ref) = world.core.get_material_ref(entity) else {
        return Err(McpResponse::Error(format!(
            "Entity '{}' has no material",
            name
        )));
    };

    let material_name = mat_ref.name.clone();
    let Some(material) = registry_entry_by_name_mut(
        &mut world.resources.material_registry.registry,
        &material_name,
    ) else {
        return Err(McpResponse::Error(format!(
            "Material '{}' not found for '{}'",
            material_name, name
        )));
    };

    if let Some(color) = base_color {
        material.base_color = color;
    }
    if let Some(em) = emissive {
        material.emissive_factor = em;
    }
    if let Some(r) = roughness {
        material.roughness = r;
    }
    if let Some(m) = metallic {
        material.metallic = m;
    }
    if let Some(mode) = alpha_mode {
        material.alpha_mode = match mode.to_lowercase().as_str() {
            "opaque" => AlphaMode::Opaque,
            "mask" => AlphaMode::Mask,
            "blend" => AlphaMode::Blend,
            _ => material.alpha_mode,
        };
    }
    if let Some(cutoff) = alpha_cutoff {
        material.alpha_cutoff = cutoff;
    }

    Ok(McpResponse::Success(format!(
        "Material updated for '{}'",
        name
    )))
}

pub(crate) fn mcp_set_casts_shadow(
    world: &mut World,
    request: crate::mcp::SetCastsShadowRequest,
) -> Result<McpResponse, McpResponse> {
    use crate::ecs::shadow::components::CastsShadow;

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

    if request.casts_shadow {
        world.core.set_casts_shadow(entity, CastsShadow);
    } else {
        world.core.remove_casts_shadow(entity);
    }

    Ok(McpResponse::Success(format!(
        "Shadow casting set to {} for '{}'",
        request.casts_shadow, request.name
    )))
}