eldiron-creator 0.9.3

A game creator for classical RPGs.
Documentation
use crate::prelude::*;
use rusterix::PixelSource;
use std::str::FromStr;

pub const CREATE_STAIRS_ACTION_ID: &str = "4f4d41d0-7f67-4c1f-a8d2-f0ab4a0be6a1";

pub struct CreateStairs {
    id: TheId,
    nodeui: TheNodeUI,
}

impl CreateStairs {
    fn parse_tile_source(text: &str) -> Option<Value> {
        Some(Value::Source(parse_tile_id_pixelsource(text)?))
    }

    fn apply_sector_stairs(&self, map: &mut Map, sector_id: u32) -> bool {
        let direction = self
            .nodeui
            .get_i32_value("actionStairsDirection")
            .unwrap_or(0)
            .clamp(0, 3);
        let steps = self
            .nodeui
            .get_i32_value("actionStairsSteps")
            .unwrap_or(6)
            .max(1);
        let total_height = self
            .nodeui
            .get_f32_value("actionStairsTotalHeight")
            .unwrap_or(1.0)
            .max(0.0);
        let fill_sides = self
            .nodeui
            .get_bool_value("actionStairsFillSides")
            .unwrap_or(true);

        let tile_id_text = self
            .nodeui
            .get_text_value("actionStairsTileId")
            .unwrap_or_default();
        let tread_tile_id_text = self
            .nodeui
            .get_text_value("actionStairsTreadTileId")
            .unwrap_or_default();
        let riser_tile_id_text = self
            .nodeui
            .get_text_value("actionStairsRiserTileId")
            .unwrap_or_default();
        let side_tile_id_text = self
            .nodeui
            .get_text_value("actionStairsSideTileId")
            .unwrap_or_default();

        let Some(sector) = map.find_sector_mut(sector_id) else {
            return false;
        };

        if total_height <= 0.0 {
            sector
                .properties
                .set("sector_feature", Value::Str("None".to_string()));
            return true;
        }

        sector
            .properties
            .set("sector_feature", Value::Str("Stairs".to_string()));
        sector
            .properties
            .set("stairs_direction", Value::Int(direction));
        sector.properties.set("stairs_steps", Value::Int(steps));
        sector
            .properties
            .set("stairs_total_height", Value::Float(total_height));
        sector
            .properties
            .set("stairs_fill_sides", Value::Bool(fill_sides));

        if let Some(src) = Self::parse_tile_source(&tile_id_text) {
            sector.properties.set("stairs_tile_source", src);
        } else {
            sector.properties.remove("stairs_tile_source");
        }
        if let Some(src) = Self::parse_tile_source(&tread_tile_id_text) {
            sector.properties.set("stairs_tread_source", src);
        } else {
            sector.properties.remove("stairs_tread_source");
        }
        if let Some(src) = Self::parse_tile_source(&riser_tile_id_text) {
            sector.properties.set("stairs_riser_source", src);
        } else {
            sector.properties.remove("stairs_riser_source");
        }
        if let Some(src) = Self::parse_tile_source(&side_tile_id_text) {
            sector.properties.set("stairs_side_source", src);
        } else {
            sector.properties.remove("stairs_side_source");
        }

        true
    }

    fn parse_tile_pixelsource(text: &str) -> Option<PixelSource> {
        match Self::parse_tile_source(text) {
            Some(Value::Source(source)) => Some(source),
            _ => None,
        }
    }
}

impl Action for CreateStairs {
    fn new() -> Self
    where
        Self: Sized,
    {
        let mut nodeui = TheNodeUI::default();

        nodeui.add_item(TheNodeUIItem::OpenTree("stairs".into()));
        nodeui.add_item(TheNodeUIItem::Selector(
            "actionStairsDirection".into(),
            "".into(),
            "".into(),
            vec!["north".into(), "east".into(), "south".into(), "west".into()],
            0,
        ));
        nodeui.add_item(TheNodeUIItem::IntEditSlider(
            "actionStairsSteps".into(),
            "".into(),
            "".into(),
            6,
            1..=64,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            "actionStairsTotalHeight".into(),
            "".into(),
            "".into(),
            1.0,
            0.0..=16.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Checkbox(
            "actionStairsFillSides".into(),
            "".into(),
            "".into(),
            true,
        ));
        nodeui.add_item(TheNodeUIItem::CloseTree);

        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionStairsTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionStairsTreadTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionStairsRiserTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionStairsSideTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::CloseTree);

        nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));

        Self {
            id: TheId::named_with_id(
                "Create Stairs",
                Uuid::from_str(CREATE_STAIRS_ACTION_ID).unwrap(),
            ),
            nodeui,
        }
    }

    fn id(&self) -> TheId {
        self.id.clone()
    }

    fn info(&self) -> String {
        "Configure non-destructive stairs on selected sectors.".to_string()
    }

    fn role(&self) -> ActionRole {
        ActionRole::Editor
    }

    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
        if server_ctx.editor_view_mode == EditorViewMode::D2 {
            return false;
        }
        !map.selected_sectors.is_empty()
    }

    fn load_params(&mut self, map: &Map) {
        let Some(sector_id) = map.selected_sectors.first() else {
            return;
        };
        let Some(sector) = map.find_sector(*sector_id) else {
            return;
        };

        self.nodeui.set_i32_value(
            "actionStairsDirection",
            sector.properties.get_int_default("stairs_direction", 0),
        );
        self.nodeui.set_i32_value(
            "actionStairsSteps",
            sector.properties.get_int_default("stairs_steps", 6),
        );
        self.nodeui.set_f32_value(
            "actionStairsTotalHeight",
            sector
                .properties
                .get_float_default("stairs_total_height", 1.0),
        );
        self.nodeui.set_bool_value(
            "actionStairsFillSides",
            sector
                .properties
                .get_bool_default("stairs_fill_sides", true),
        );

        let tile_id_text = source_to_text(sector.properties.get("stairs_tile_source"));
        let tread_tile_id_text = source_to_text(sector.properties.get("stairs_tread_source"));
        let riser_tile_id_text = source_to_text(sector.properties.get("stairs_riser_source"));
        let side_tile_id_text = source_to_text(sector.properties.get("stairs_side_source"));

        self.nodeui
            .set_text_value("actionStairsTileId", tile_id_text);
        self.nodeui
            .set_text_value("actionStairsTreadTileId", tread_tile_id_text);
        self.nodeui
            .set_text_value("actionStairsRiserTileId", riser_tile_id_text);
        self.nodeui
            .set_text_value("actionStairsSideTileId", side_tile_id_text);
    }

    fn apply(
        &self,
        map: &mut Map,
        _ui: &mut TheUI,
        _ctx: &mut TheContext,
        server_ctx: &mut ServerContext,
    ) -> Option<ProjectUndoAtom> {
        let prev = map.clone();
        let mut changed = false;

        for sector_id in map.selected_sectors.clone() {
            changed |= self.apply_sector_stairs(map, sector_id);
        }

        if changed {
            Some(ProjectUndoAtom::MapEdit(
                server_ctx.pc,
                Box::new(prev),
                Box::new(map.clone()),
            ))
        } else {
            None
        }
    }

    fn params(&self) -> TheNodeUI {
        self.nodeui.clone()
    }

    fn hud_material_slots(
        &self,
        _map: &Map,
        _server_ctx: &ServerContext,
    ) -> Option<Vec<ActionMaterialSlot>> {
        let all = self
            .nodeui
            .get_text_value("actionStairsTileId")
            .unwrap_or_default();
        let tread = self
            .nodeui
            .get_text_value("actionStairsTreadTileId")
            .unwrap_or_default();
        let riser = self
            .nodeui
            .get_text_value("actionStairsRiserTileId")
            .unwrap_or_default();
        let side = self
            .nodeui
            .get_text_value("actionStairsSideTileId")
            .unwrap_or_default();
        let all_source = Self::parse_tile_pixelsource(&all);
        Some(vec![
            ActionMaterialSlot {
                label: "STAIR".to_string(),
                source: all_source.clone(),
            },
            ActionMaterialSlot {
                label: "TREAD".to_string(),
                source: Self::parse_tile_pixelsource(&tread).or(all_source.clone()),
            },
            ActionMaterialSlot {
                label: "RISER".to_string(),
                source: Self::parse_tile_pixelsource(&riser).or(all_source.clone()),
            },
            ActionMaterialSlot {
                label: "SIDE".to_string(),
                source: Self::parse_tile_pixelsource(&side).or(all_source),
            },
        ])
    }

    fn set_hud_material_from_tile(
        &mut self,
        _map: &Map,
        _server_ctx: &ServerContext,
        slot_index: i32,
        tile_id: Uuid,
    ) -> bool {
        let value = tile_id.to_string();
        match slot_index {
            0 => self.nodeui.set_text_value("actionStairsTileId", value),
            1 => self.nodeui.set_text_value("actionStairsTreadTileId", value),
            2 => self.nodeui.set_text_value("actionStairsRiserTileId", value),
            3 => self.nodeui.set_text_value("actionStairsSideTileId", value),
            _ => return false,
        }
        true
    }

    fn clear_hud_material_slot(
        &mut self,
        _map: &Map,
        _server_ctx: &ServerContext,
        slot_index: i32,
    ) -> bool {
        match slot_index {
            0 => self
                .nodeui
                .set_text_value("actionStairsTileId", String::new()),
            1 => self
                .nodeui
                .set_text_value("actionStairsTreadTileId", String::new()),
            2 => self
                .nodeui
                .set_text_value("actionStairsRiserTileId", String::new()),
            3 => self
                .nodeui
                .set_text_value("actionStairsSideTileId", String::new()),
            _ => return false,
        }
        true
    }

    fn handle_event(
        &mut self,
        event: &TheEvent,
        _project: &mut Project,
        _ui: &mut TheUI,
        _ctx: &mut TheContext,
        _server_ctx: &mut ServerContext,
    ) -> bool {
        self.nodeui.handle_event(event)
    }
}