use crate::prelude::*;
use std::str::FromStr;
pub const CREATE_FENCE_ACTION_ID: &str = "1d4cc6df-66fd-4d28-a340-c702f696f7b8";
pub struct CreateFence {
id: TheId,
nodeui: TheNodeUI,
}
impl CreateFence {
fn apply_linedef_feature(&self, map: &mut Map, linedef_id: u32) -> bool {
let spacing = self
.nodeui
.get_f32_value("actionLayoutSpacing")
.unwrap_or(1.5)
.max(0.1);
let post_size = self
.nodeui
.get_f32_value("actionPostSize")
.unwrap_or(0.18)
.max(0.02);
let post_shape = self.nodeui.get_i32_value("actionPostShape").unwrap_or(0);
let post_height = self.nodeui.get_f32_value("actionPostHeight").unwrap_or(2.0);
let round_segments = self
.nodeui
.get_i32_value("actionPostRoundSegments")
.unwrap_or(8)
.max(3);
let connector_count = self
.nodeui
.get_i32_value("actionConnectorCount")
.unwrap_or(2)
.max(0);
let connector_style = self
.nodeui
.get_i32_value("actionConnectorStyle")
.unwrap_or(0);
let connector_size = self
.nodeui
.get_f32_value("actionConnectorSize")
.unwrap_or(0.12)
.max(0.01);
let connector_drop = self
.nodeui
.get_f32_value("actionConnectorDrop")
.unwrap_or(1.2)
.max(0.0);
let lean_amount = self
.nodeui
.get_f32_value("actionLeanAmount")
.unwrap_or(0.0)
.max(0.0);
let lean_randomness = self
.nodeui
.get_f32_value("actionLeanRandomness")
.unwrap_or(1.0)
.clamp(0.0, 1.0);
let tile_id_text = self
.nodeui
.get_text_value("actionMaterialTileId")
.unwrap_or_default();
let tile_source = parse_tile_id_pixelsource(&tile_id_text);
let Some(linedef) = map.find_linedef_mut(linedef_id) else {
return false;
};
if post_height <= 0.0 {
linedef
.properties
.set("linedef_feature", Value::Str("None".to_string()));
return true;
}
linedef
.properties
.set("linedef_feature", Value::Str("Fence".to_string()));
linedef
.properties
.set("feature_layout_spacing", Value::Float(spacing));
linedef
.properties
.set("feature_post_size", Value::Float(post_size));
linedef
.properties
.set("feature_post_shape", Value::Int(post_shape));
linedef
.properties
.set("feature_height", Value::Float(post_height));
linedef
.properties
.set("feature_round_segments", Value::Int(round_segments));
linedef
.properties
.set("feature_connector_count", Value::Int(connector_count));
linedef
.properties
.set("feature_connector_style", Value::Int(connector_style));
linedef
.properties
.set("feature_connector_size", Value::Float(connector_size));
linedef
.properties
.set("feature_connector_drop", Value::Float(connector_drop));
linedef
.properties
.set("feature_lean_amount", Value::Float(lean_amount));
linedef
.properties
.set("feature_lean_randomness", Value::Float(lean_randomness));
if let Some(source) = tile_source {
linedef
.properties
.set("feature_source", Value::Source(source));
} else {
linedef.properties.remove("feature_source");
}
true
}
}
impl Action for CreateFence {
fn new() -> Self
where
Self: Sized,
{
let mut nodeui = TheNodeUI::default();
nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
nodeui.add_item(TheNodeUIItem::Text(
"actionMaterialTileId".into(),
"".into(),
"".into(),
"".into(),
None,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
nodeui.add_item(TheNodeUIItem::OpenTree("layout".into()));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionLayoutSpacing".into(),
"".into(),
"".into(),
1.5,
0.1..=8.0,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
nodeui.add_item(TheNodeUIItem::OpenTree("posts".into()));
nodeui.add_item(TheNodeUIItem::Selector(
"actionPostShape".into(),
"".into(),
"".into(),
vec!["square".into(), "round".into()],
0,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionPostSize".into(),
"".into(),
"".into(),
0.18,
0.02..=2.0,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionPostHeight".into(),
"".into(),
"".into(),
2.0,
0.0..=8.0,
false,
));
nodeui.add_item(TheNodeUIItem::IntEditSlider(
"actionPostRoundSegments".into(),
"".into(),
"".into(),
8,
3..=24,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
nodeui.add_item(TheNodeUIItem::OpenTree("connectors".into()));
nodeui.add_item(TheNodeUIItem::IntEditSlider(
"actionConnectorCount".into(),
"".into(),
"".into(),
2,
0..=8,
false,
));
nodeui.add_item(TheNodeUIItem::Selector(
"actionConnectorStyle".into(),
"".into(),
"".into(),
vec!["plank".into(), "square".into(), "round".into()],
0,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionConnectorSize".into(),
"".into(),
"".into(),
0.12,
0.01..=1.0,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionConnectorDrop".into(),
"".into(),
"".into(),
1.2,
0.0..=6.0,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
nodeui.add_item(TheNodeUIItem::OpenTree("lean".into()));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionLeanAmount".into(),
"".into(),
"".into(),
0.0,
0.0..=1.5,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionLeanRandomness".into(),
"".into(),
"".into(),
1.0,
0.0..=1.0,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));
Self {
id: TheId::named_with_id(
"Create Fence",
Uuid::from_str(CREATE_FENCE_ACTION_ID).unwrap(),
),
nodeui,
}
}
fn id(&self) -> TheId {
self.id.clone()
}
fn info(&self) -> String {
"Configure a non-destructive fence feature on selected linedefs.".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() && !map.selected_linedefs.is_empty()
}
fn load_params(&mut self, map: &Map) {
let Some(linedef_id) = map.selected_linedefs.first() else {
return;
};
let Some(linedef) = map.find_linedef(*linedef_id) else {
return;
};
self.nodeui.set_f32_value(
"actionLayoutSpacing",
linedef
.properties
.get_float_default("feature_layout_spacing", 1.5),
);
self.nodeui.set_i32_value(
"actionPostShape",
linedef.properties.get_int_default("feature_post_shape", 0),
);
self.nodeui.set_f32_value(
"actionPostSize",
linedef
.properties
.get_float_default("feature_post_size", 0.18),
);
self.nodeui.set_f32_value(
"actionPostHeight",
linedef.properties.get_float_default("feature_height", 2.0),
);
self.nodeui.set_i32_value(
"actionPostRoundSegments",
linedef
.properties
.get_int_default("feature_round_segments", 8),
);
self.nodeui.set_i32_value(
"actionConnectorCount",
linedef
.properties
.get_int_default("feature_connector_count", 2),
);
self.nodeui.set_i32_value(
"actionConnectorStyle",
linedef
.properties
.get_int_default("feature_connector_style", 0),
);
self.nodeui.set_f32_value(
"actionConnectorSize",
linedef
.properties
.get_float_default("feature_connector_size", 0.12),
);
self.nodeui.set_f32_value(
"actionConnectorDrop",
linedef
.properties
.get_float_default("feature_connector_drop", 1.2),
);
self.nodeui.set_f32_value(
"actionLeanAmount",
linedef
.properties
.get_float_default("feature_lean_amount", 0.0),
);
self.nodeui.set_f32_value(
"actionLeanRandomness",
linedef
.properties
.get_float_default("feature_lean_randomness", 1.0),
);
let tile_id_text = source_to_text(linedef.properties.get("feature_source"));
self.nodeui
.set_text_value("actionMaterialTileId", 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 linedef_id in map.selected_linedefs.clone() {
changed |= self.apply_linedef_feature(map, linedef_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>> {
Some(vec![ActionMaterialSlot {
label: "MAT".to_string(),
source: parse_tile_id_pixelsource(
&self
.nodeui
.get_text_value("actionMaterialTileId")
.unwrap_or_default(),
),
}])
}
fn set_hud_material_from_tile(
&mut self,
_map: &Map,
_server_ctx: &ServerContext,
slot_index: i32,
tile_id: Uuid,
) -> bool {
if slot_index != 0 {
return false;
}
self.nodeui
.set_text_value("actionMaterialTileId", tile_id.to_string());
true
}
fn clear_hud_material_slot(
&mut self,
_map: &Map,
_server_ctx: &ServerContext,
slot_index: i32,
) -> bool {
if slot_index != 0 {
return false;
}
self.nodeui
.set_text_value("actionMaterialTileId", String::new());
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)
}
}