use crate::prelude::*;
use rusterix::PixelSource;
use std::str::FromStr;
pub const EDIT_LINEDEF_ACTION_ID: &str = "284638fa-5769-442a-a55e-88121a37f193";
pub struct EditLinedef {
id: TheId,
nodeui: TheNodeUI,
show_terrain: bool,
}
impl EditLinedef {
fn build_nodeui(show_terrain: bool) -> TheNodeUI {
let mut nodeui: TheNodeUI = TheNodeUI::default();
let item = TheNodeUIItem::Text(
"actionLinedefName".into(),
"".into(),
"".into(),
"".into(),
None,
false,
);
nodeui.add_item(item);
if show_terrain {
nodeui.add_item(TheNodeUIItem::OpenTree("terrain".into()));
nodeui.add_item(TheNodeUIItem::Checkbox(
"actionTerrainSmooth".into(),
"".into(),
"".into(),
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionTerrainWidth".into(),
"".into(),
"".into(),
2.0,
0.0..=128.0,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionTerrainFalloffDistance".into(),
"".into(),
"".into(),
3.0,
0.0..=128.0,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionTerrainFalloffSteepness".into(),
"".into(),
"".into(),
2.0,
0.1..=16.0,
false,
));
nodeui.add_item(TheNodeUIItem::Icons(
"actionTerrainTile".into(),
"".into(),
"".into(),
vec![(
TheRGBABuffer::new(TheDim::sized(36, 36)),
"".to_string(),
Uuid::nil(),
)],
));
nodeui.add_item(TheNodeUIItem::Text(
"actionTileId".into(),
"".into(),
"".into(),
"".into(),
None,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionTerrainTileFalloff".into(),
"".into(),
"".into(),
1.0,
0.0..=16.0,
false,
));
nodeui.add_item(TheNodeUIItem::FloatEditSlider(
"actionTerrainRoadOrganic".into(),
"".into(),
"".into(),
0.0,
0.0..=1.0,
false,
));
nodeui.add_item(TheNodeUIItem::CloseTree);
}
let item = TheNodeUIItem::Markdown("desc".into(), "".into());
nodeui.add_item(item);
nodeui
}
}
impl Action for EditLinedef {
fn new() -> Self
where
Self: Sized,
{
let nodeui = Self::build_nodeui(true);
Self {
id: TheId::named_with_id(
&fl!("action_edit_linedef"),
Uuid::from_str(EDIT_LINEDEF_ACTION_ID).unwrap(),
),
nodeui,
show_terrain: true,
}
}
fn id(&self) -> TheId {
self.id.clone()
}
fn info(&self) -> String {
fl!("action_edit_linedef_desc")
}
fn role(&self) -> ActionRole {
ActionRole::Editor
}
fn accel(&self) -> Option<TheAccelerator> {
None
}
fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, _server_ctx: &ServerContext) -> bool {
!map.selected_linedefs.is_empty()
}
fn load_params(&mut self, map: &Map) {
if let Some(linedef_id) = map.selected_linedefs.first() {
if let Some(linedef) = map.find_linedef(*linedef_id) {
self.nodeui
.set_text_value("actionLinedefName", linedef.name.clone());
self.nodeui.set_bool_value(
"actionTerrainSmooth",
linedef.properties.get_bool_default("terrain_smooth", false),
);
self.nodeui.set_f32_value(
"actionTerrainWidth",
linedef.properties.get_float_default("terrain_width", 2.0),
);
self.nodeui.set_f32_value(
"actionTerrainFalloffDistance",
linedef
.properties
.get_float_default("terrain_falloff_distance", 3.0),
);
self.nodeui.set_f32_value(
"actionTerrainFalloffSteepness",
linedef
.properties
.get_float_default("terrain_falloff_steepness", 2.0),
);
self.nodeui.set_f32_value(
"actionTerrainTileFalloff",
linedef
.properties
.get_float_default("terrain_tile_falloff", 1.0),
);
self.nodeui.set_f32_value(
"actionTerrainRoadOrganic",
linedef
.properties
.get_float_default("terrain_road_organic", 0.0),
);
let terrain_tile_id = if let Some(Value::Source(PixelSource::TileId(id))) =
linedef.properties.get("terrain_source")
{
*id
} else {
Uuid::nil()
};
self.nodeui.set_text_value(
"actionTileId",
if terrain_tile_id == Uuid::nil() {
String::new()
} else {
terrain_tile_id.to_string()
},
);
if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
&& let TheNodeUIItem::Icons(_, _, _, items) = item
&& items.len() == 1
{
items[0].2 = terrain_tile_id;
}
}
}
}
fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {
let show_terrain = server_ctx.get_map_context() == MapContext::Region;
if show_terrain != self.show_terrain {
let name = self
.nodeui
.get_text_value("actionLinedefName")
.unwrap_or_default();
let terrain_smooth = self
.nodeui
.get_bool_value("actionTerrainSmooth")
.unwrap_or(false);
let terrain_width = self
.nodeui
.get_f32_value("actionTerrainWidth")
.unwrap_or(2.0);
let terrain_falloff_distance = self
.nodeui
.get_f32_value("actionTerrainFalloffDistance")
.unwrap_or(3.0);
let terrain_falloff_steepness = self
.nodeui
.get_f32_value("actionTerrainFalloffSteepness")
.unwrap_or(2.0);
let terrain_tile_falloff = self
.nodeui
.get_f32_value("actionTerrainTileFalloff")
.unwrap_or(1.0);
let terrain_road_organic = self
.nodeui
.get_f32_value("actionTerrainRoadOrganic")
.unwrap_or(0.0);
let tile_id_text = self
.nodeui
.get_text_value("actionTileId")
.unwrap_or_default();
let terrain_tile_id = self
.nodeui
.get_tile_id("actionTerrainTile", 0)
.unwrap_or(Uuid::nil());
self.nodeui = Self::build_nodeui(show_terrain);
self.show_terrain = show_terrain;
self.nodeui.set_text_value("actionLinedefName", name);
self.nodeui
.set_bool_value("actionTerrainSmooth", terrain_smooth);
self.nodeui
.set_f32_value("actionTerrainWidth", terrain_width);
self.nodeui
.set_f32_value("actionTerrainFalloffDistance", terrain_falloff_distance);
self.nodeui
.set_f32_value("actionTerrainFalloffSteepness", terrain_falloff_steepness);
self.nodeui
.set_f32_value("actionTerrainTileFalloff", terrain_tile_falloff);
self.nodeui
.set_f32_value("actionTerrainRoadOrganic", terrain_road_organic);
self.nodeui.set_text_value("actionTileId", tile_id_text);
if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
&& let TheNodeUIItem::Icons(_, _, _, items) = item
&& items.len() == 1
{
items[0].2 = terrain_tile_id;
}
}
let mut tile_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
let mut tile_id = Uuid::nil();
if let Some(map) = project.get_map(server_ctx)
&& let Some(linedef_id) = map.selected_linedefs.first()
&& let Some(linedef) = map.find_linedef(*linedef_id)
&& let Some(Value::Source(PixelSource::TileId(id))) =
linedef.properties.get("terrain_source")
&& let Some(tile) = project.tiles.get(id)
&& !tile.is_empty()
{
tile_icon = tile.textures[0].to_rgba();
tile_id = *id;
}
if let Some(item) = self.nodeui.get_item_mut("actionTerrainTile")
&& let TheNodeUIItem::Icons(_, _, _, items) = item
&& items.len() == 1
{
items[0].0 = tile_icon;
items[0].2 = tile_id;
}
}
fn apply(
&self,
map: &mut Map,
_ui: &mut TheUI,
_ctx: &mut TheContext,
server_ctx: &mut ServerContext,
) -> Option<ProjectUndoAtom> {
let mut changed = false;
let prev = map.clone();
let name = self
.nodeui
.get_text_value("actionLinedefName")
.unwrap_or(String::new());
let terrain_smooth = self
.nodeui
.get_bool_value("actionTerrainSmooth")
.unwrap_or(false);
let terrain_width = self
.nodeui
.get_f32_value("actionTerrainWidth")
.unwrap_or(2.0);
let terrain_falloff_distance = self
.nodeui
.get_f32_value("actionTerrainFalloffDistance")
.unwrap_or(3.0);
let terrain_falloff_steepness = self
.nodeui
.get_f32_value("actionTerrainFalloffSteepness")
.unwrap_or(2.0);
let terrain_tile_falloff = self
.nodeui
.get_f32_value("actionTerrainTileFalloff")
.unwrap_or(1.0);
let terrain_road_organic = self
.nodeui
.get_f32_value("actionTerrainRoadOrganic")
.unwrap_or(0.0);
let terrain_tile_id = self.nodeui.get_tile_id("actionTerrainTile", 0);
let tile_id_text = self
.nodeui
.get_text_value("actionTileId")
.unwrap_or_default();
let terrain_source = parse_tile_id_pixelsource(&tile_id_text)
.or_else(|| terrain_tile_id.map(PixelSource::TileId));
for linedef_id in map.selected_linedefs.clone() {
if let Some(linedef) = map.find_linedef_mut(linedef_id) {
if name != linedef.name {
linedef.name = name.clone();
changed = true;
}
if linedef.properties.get_bool_default("terrain_smooth", false) != terrain_smooth {
linedef
.properties
.set("terrain_smooth", Value::Bool(terrain_smooth));
changed = true;
}
if (linedef.properties.get_float_default("terrain_width", 2.0) - terrain_width)
.abs()
> 0.0001
{
linedef
.properties
.set("terrain_width", Value::Float(terrain_width));
changed = true;
}
if (linedef
.properties
.get_float_default("terrain_falloff_distance", 3.0)
- terrain_falloff_distance)
.abs()
> 0.0001
{
linedef.properties.set(
"terrain_falloff_distance",
Value::Float(terrain_falloff_distance),
);
changed = true;
}
if (linedef
.properties
.get_float_default("terrain_falloff_steepness", 2.0)
- terrain_falloff_steepness)
.abs()
> 0.0001
{
linedef.properties.set(
"terrain_falloff_steepness",
Value::Float(terrain_falloff_steepness),
);
changed = true;
}
if (linedef
.properties
.get_float_default("terrain_tile_falloff", 1.0)
- terrain_tile_falloff)
.abs()
> 0.0001
{
linedef.properties.set(
"terrain_tile_falloff",
Value::Float(terrain_tile_falloff.max(0.0)),
);
changed = true;
}
if (linedef
.properties
.get_float_default("terrain_road_organic", 0.0)
- terrain_road_organic)
.abs()
> 0.0001
{
linedef.properties.set(
"terrain_road_organic",
Value::Float(terrain_road_organic.clamp(0.0, 1.0)),
);
changed = true;
}
match terrain_source.as_ref() {
Some(source) => {
let has_changed = match linedef.properties.get("terrain_source") {
Some(Value::Source(existing)) => existing != source,
_ => true,
};
if has_changed {
linedef
.properties
.set("terrain_source", Value::Source(source.clone()));
changed = true;
}
}
_ => {
if linedef.properties.contains("terrain_source") {
linedef.properties.remove("terrain_source");
changed = true;
}
}
}
}
}
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>> {
if let Some(slots) = crate::actions::builder_hud_material_slots_for_selected_linedef(map) {
return Some(slots);
}
if !self.show_terrain {
return None;
}
let icon_id = self
.nodeui
.get_tile_id("actionTerrainTile", 0)
.unwrap_or(Uuid::nil());
Some(vec![ActionMaterialSlot {
label: "TERRAIN".to_string(),
source: {
let text = self
.nodeui
.get_text_value("actionTileId")
.unwrap_or_default();
if let Some(source) = parse_tile_id_pixelsource(&text) {
Some(source)
} else if icon_id != Uuid::nil() {
Some(PixelSource::TileId(icon_id))
} else {
None
}
},
}])
}
fn set_hud_material_from_tile(
&mut self,
_map: &Map,
_server_ctx: &ServerContext,
slot_index: i32,
tile_id: Uuid,
) -> bool {
if !self.show_terrain || slot_index != 0 {
return false;
}
self.nodeui
.set_text_value("actionTileId", tile_id.to_string());
set_nodeui_icon_tile_id(&mut self.nodeui, "actionTerrainTile", 0, tile_id);
true
}
fn clear_hud_material_slot(
&mut self,
_map: &Map,
_server_ctx: &ServerContext,
slot_index: i32,
) -> bool {
if !self.show_terrain || slot_index != 0 {
return false;
}
self.nodeui.set_text_value("actionTileId", String::new());
clear_nodeui_icon_tile_id(&mut self.nodeui, "actionTerrainTile", 0);
true
}
fn handle_event(
&mut self,
event: &TheEvent,
project: &mut Project,
_ui: &mut TheUI,
ctx: &mut TheContext,
_server_ctx: &mut ServerContext,
) -> bool {
if let TheEvent::TileDropped(id, tile_id, index) = event
&& let Some(item) = self.nodeui.get_item_mut(&id.name)
&& let TheNodeUIItem::Icons(_, _, _, items) = item
&& *index < items.len()
&& let Some(tile) = project.tiles.get(tile_id)
&& !tile.is_empty()
{
items[*index].0 = tile.textures[0].to_rgba();
items[*index].2 = *tile_id;
self.nodeui
.set_text_value("actionTileId", tile_id.to_string());
ctx.ui.send(TheEvent::Custom(
TheId::named("Update Action List"),
TheValue::Empty,
));
return true;
}
self.nodeui.handle_event(event)
}
}