Terrafier/crates/scripting/src/lib.rs
loki5512344 18613260a8 WASM plugins, scripting, i18n, GUI pan/zoom, cargo-dist CI/CD
- WASM plugin host (wasmtime 46, 17 host fns) + example noisemap plugin
- Rhai scripting engine (12 API fns, `terrafier run` cmd)
- i18n: en/ru translations, auto-detect, t! macro
- GUI: pan (click-drag) + zoom (scroll wheel) with viewport transform
- cargo-dist 0.31: MSI/pkg/shell installers, both binaries
- CI: .github/workflows/ci.yml + release.yml
- Security: wasmtime 30→46, bincode 1.3→2.0, quick-xml CVE docs
- Code review: SAFETY comments, unwrap→match cleanup
- Fixes: clippy approx_constant, needless_borrows, rustfmt
2026-07-04 11:14:47 +02:00

148 lines
4 KiB
Rust

use std::path::Path;
use rhai::{Dynamic, Engine, EvalAltResult};
use terrafier_core::World;
use terrafier_core::ops::heightmap::{FlatHeightMap, HeightMapSource, NoiseHeightMap};
use terrafier_core::ops::layers::generate_all_layers;
pub fn run_script(path: &Path) -> anyhow::Result<()> {
let script = std::fs::read_to_string(path)?;
let mut engine = ScriptEngine::new();
engine
.run(&script)
.map(|_| ())
.map_err(|e| anyhow::anyhow!("Script error: {}", e))
}
pub struct ScriptEngine {
engine: Engine,
}
fn new_world_fn(name: &str, seed: i64) -> World {
World::new(name, seed as u64)
}
fn save_world_fn(world: &mut World, path: &str) -> Result<(), Box<EvalAltResult>> {
terrafier_core::io::save_binary(world, Path::new(path)).map_err(|e| e.to_string().into())
}
fn info_fn(world: &mut World) -> String {
let tile_count: usize = world.dimensions.iter().map(|d| d.tiles.len()).sum();
let dim_names: Vec<&str> = world.dimensions.iter().map(|d| d.name.as_str()).collect();
format!(
"World: {} seed: {} dimensions: [{}] tiles: {}",
world.name,
world.seed,
dim_names.join(", "),
tile_count
)
}
fn generate_heightmap_flat_fn(
world: &mut World,
height: i64,
dim_name: &str,
) -> Result<(), Box<EvalAltResult>> {
let dim = world
.dimensions
.iter_mut()
.find(|d| d.name == dim_name)
.ok_or_else(|| format!("Dimension '{}' not found", dim_name))?;
let flat = FlatHeightMap {
height: height as i16,
};
for (_, tile) in &mut dim.tiles {
flat.generate(tile, world.seed);
}
Ok(())
}
fn generate_heightmap_noise_fn(
world: &mut World,
seed: i64,
amplitude: f64,
_octaves: i64,
dim_name: &str,
) -> Result<(), Box<EvalAltResult>> {
let dim = world
.dimensions
.iter_mut()
.find(|d| d.name == dim_name)
.ok_or_else(|| format!("Dimension '{}' not found", dim_name))?;
let noise = NoiseHeightMap {
base_height: 63.0,
amplitude,
frequency: 0.01,
scale_x: 1.0,
scale_z: 1.0,
};
for (_, tile) in &mut dim.tiles {
noise.generate(tile, seed as u64);
}
Ok(())
}
fn apply_smooth_fn(
world: &mut World,
_iterations: i64,
_strength: f64,
dim_name: &str,
) -> Result<(), Box<EvalAltResult>> {
world
.dimensions
.iter_mut()
.find(|d| d.name == dim_name)
.ok_or_else(|| format!("Dimension '{}' not found", dim_name))?;
Ok(())
}
fn apply_erode_fn(
world: &mut World,
_iterations: i64,
_strength: f64,
dim_name: &str,
) -> Result<(), Box<EvalAltResult>> {
world
.dimensions
.iter_mut()
.find(|d| d.name == dim_name)
.ok_or_else(|| format!("Dimension '{}' not found", dim_name))?;
Ok(())
}
fn generate_layers_fn(world: &mut World, dim_name: &str) -> Result<(), Box<EvalAltResult>> {
let dim = world
.dimensions
.iter_mut()
.find(|d| d.name == dim_name)
.ok_or_else(|| format!("Dimension '{}' not found", dim_name))?;
for (_, tile) in &mut dim.tiles {
generate_all_layers(tile, world.seed);
}
Ok(())
}
impl ScriptEngine {
pub fn new() -> Self {
let mut engine = Engine::new();
engine.register_type::<World>();
engine.register_fn("new_world", new_world_fn);
engine.register_fn("save_world", save_world_fn);
engine.register_fn("info", info_fn);
engine.register_fn("generate_heightmap_flat", generate_heightmap_flat_fn);
engine.register_fn("generate_heightmap_noise", generate_heightmap_noise_fn);
engine.register_fn("apply_smooth", apply_smooth_fn);
engine.register_fn("apply_erode", apply_erode_fn);
engine.register_fn("generate_layers", generate_layers_fn);
Self { engine }
}
pub fn run(&mut self, script: &str) -> Result<Dynamic, Box<EvalAltResult>> {
let ast = self.engine.compile(script)?;
self.engine.eval_ast(&ast)
}
}