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
This commit is contained in:
loki5512344 2026-07-04 11:14:47 +02:00
parent bdd4564240
commit 18613260a8
100 changed files with 4262 additions and 585 deletions

View file

@ -73,7 +73,10 @@ fn terrafier_biome_name(id: u8) -> &'static str {
arr
});
names.get(id as usize).copied().unwrap_or("minecraft:plains")
names
.get(id as usize)
.copied()
.unwrap_or("minecraft:plains")
}
pub struct BiomeLayerExport;

View file

@ -57,10 +57,10 @@ pub fn biome_name(
"minecraft:plains"
}
mod biome_res;
mod caves_frost;
mod river_trees;
mod biome_res;
pub use biome_res::{BiomeLayerExport, ResourcesLayerExport};
pub use caves_frost::{CavesLayerExport, FrostLayerExport};
pub use river_trees::{RiverLayerExport, TreesLayerExport};
pub use biome_res::{BiomeLayerExport, ResourcesLayerExport};

View file

@ -1,9 +1,9 @@
use std::collections::{BTreeSet, HashMap};
use crate::io::layer_export::{apply_layers, biome_name, LayerExport};
use crate::io::minecraft::{data_version, Result};
use crate::model::tile::Tile;
use super::blocks::{bits_needed, pack_indices, BLOCK_SET, BLOCK_SET_EXTENDED};
use super::block_name;
use super::blocks::{BLOCK_SET, BLOCK_SET_EXTENDED, bits_needed, pack_indices};
use crate::io::layer_export::{LayerExport, apply_layers, biome_name};
use crate::io::minecraft::{Result, data_version};
use crate::model::tile::Tile;
use std::collections::{BTreeSet, HashMap};
fn make_section(
sec_y: i32,
@ -12,7 +12,10 @@ fn make_section(
) -> terrafier_nbt::Tag {
let mut sec = HashMap::new();
sec.insert("Y".into(), terrafier_nbt::Tag::Byte(sec_y as i8));
sec.insert("block_states".into(), terrafier_nbt::Tag::Compound(block_states));
sec.insert(
"block_states".into(),
terrafier_nbt::Tag::Compound(block_states),
);
sec.insert("biomes".into(), terrafier_nbt::Tag::Compound(biomes));
terrafier_nbt::Tag::Compound(sec)
}
@ -38,7 +41,10 @@ pub(super) fn build_chunk_nbt(
let mut compound = HashMap::new();
compound.insert("xPos".into(), terrafier_nbt::Tag::Int(chunk_x));
compound.insert("zPos".into(), terrafier_nbt::Tag::Int(chunk_z));
compound.insert("DataVersion".into(), terrafier_nbt::Tag::Int(data_version()));
compound.insert(
"DataVersion".into(),
terrafier_nbt::Tag::Int(data_version()),
);
compound.insert("Status".into(), terrafier_nbt::Tag::String("full".into()));
let min_sec = (tile.min_height as i32 >> 4).max(-4);
let max_sec = (tile.max_height as i32 >> 4).min(20);
@ -104,7 +110,10 @@ pub(super) fn build_chunk_nbt_with_layers(
let mut compound = HashMap::new();
compound.insert("xPos".into(), terrafier_nbt::Tag::Int(chunk_x));
compound.insert("zPos".into(), terrafier_nbt::Tag::Int(chunk_z));
compound.insert("DataVersion".into(), terrafier_nbt::Tag::Int(data_version()));
compound.insert(
"DataVersion".into(),
terrafier_nbt::Tag::Int(data_version()),
);
compound.insert("Status".into(), terrafier_nbt::Tag::String("full".into()));
let min_sec = (tile.min_height as i32 >> 4).max(-4);
let max_sec = (tile.max_height as i32 >> 4).min(20);
@ -125,7 +134,12 @@ pub(super) fn build_chunk_nbt_with_layers(
let terrain_id = tile.terrain[tile_lz * 128 + tile_lx];
let base_name = block_name(terrain_id, global_y, surface_y);
let name = apply_layers(
tile, tile_lx, tile_lz, global_y, surface_y, base_name,
tile,
tile_lx,
tile_lz,
global_y,
surface_y,
base_name,
layer_exporters,
);
BLOCK_SET_EXTENDED

View file

@ -2,6 +2,6 @@ mod blocks;
mod chunk;
mod writer;
pub(crate) use blocks::block_name;
pub use writer::save_world;
pub use writer::save_world_with_layers;
pub(crate) use blocks::block_name;

View file

@ -5,7 +5,7 @@ use std::fs;
use std::path::Path;
use crate::io::layer_export::LayerExport;
use crate::io::minecraft::{data_version, Result};
use crate::io::minecraft::{Result, data_version};
use crate::model::tile::Tile;
use crate::model::world::World;
@ -47,8 +47,7 @@ pub fn save_world(world: &World, output_path: &Path) -> Result<()> {
let region_local_x = (chunk_x & 31) as u8;
let region_local_z = (chunk_z & 31) as u8;
let chunk_data =
build_chunk_nbt(chunk_x, chunk_z, tile, chunk_lx, chunk_lz)?;
let chunk_data = build_chunk_nbt(chunk_x, chunk_z, tile, chunk_lx, chunk_lz)?;
if chunk_data.is_empty() {
continue;

View file

@ -2,12 +2,12 @@ mod export;
mod reader;
mod version;
pub use reader::load_save;
pub use reader::discover_region_files;
pub use export::save_world;
pub use export::save_world_with_layers;
pub use version::version_from_data_version;
pub use reader::discover_region_files;
pub use reader::load_save;
pub(crate) use version::data_version;
pub use version::version_from_data_version;
use thiserror::Error;

View file

@ -51,7 +51,19 @@ pub fn load_save(path: &Path) -> Result<World> {
let region_bytes = fs::read(&path)?;
let region = Region::from_bytes(rx, rz, &region_bytes)?;
for (local_x, local_z) in region.chunk_coords() {
let chunk_data = region.get_chunk_data(local_x, local_z).unwrap();
let chunk_data = match region.get_chunk_data(local_x, local_z) {
Some(data) => data,
None => {
log::warn!(
"corrupted chunk data at ({}, {}) in region ({}, {})",
local_x,
local_z,
rx,
rz
);
continue;
}
};
if let Ok(chunk_tag) = read_gzip(chunk_data)
&& let Some(chunk) =
terrafier_fastanvil::io::chunk::Chunk::from_nbt(&chunk_tag)

View file

@ -26,7 +26,9 @@ pub type Result<T> = std::result::Result<T, BinaryError>;
/// Save a `World` to a binary file.
pub fn save_binary(world: &World, path: &Path) -> Result<()> {
let bytes = bincode::serialize(world).map_err(|e| BinaryError::Serialize(e.to_string()))?;
let config = bincode::config::standard();
let bytes =
bincode::serde::encode_to_vec(world, config).map_err(|e| BinaryError::Serialize(e.to_string()))?;
std::fs::write(path, &bytes)?;
Ok(())
@ -35,8 +37,10 @@ pub fn save_binary(world: &World, path: &Path) -> Result<()> {
/// Load a `World` from a binary file.
pub fn load_binary(path: &Path) -> Result<World> {
let bytes = std::fs::read(path)?;
let world: World =
bincode::deserialize(&bytes).map_err(|e| BinaryError::Deserialize(e.to_string()))?;
let config = bincode::config::standard();
let (world, _): (World, usize) =
bincode::serde::decode_from_slice(&bytes, config)
.map_err(|e| BinaryError::Deserialize(e.to_string()))?;
Ok(world)
}

View file

@ -1,5 +1,5 @@
use super::types::{
ClassDesc, JvmValue, BASE_WIRE_HANDLE, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, TC_CLASS,
BASE_WIRE_HANDLE, ClassDesc, JvmValue, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG, TC_CLASS,
TC_ENUM, TC_LONGSTRING, TC_NULL, TC_OBJECT, TC_REFERENCE, TC_RESET, TC_STRING,
};

View file

@ -15,8 +15,8 @@ pub use extract::*;
pub use types::WpImportError;
use crate::model::dimension::Dimension;
use crate::model::types::Platform;
use crate::model::tile::Tile;
use crate::model::types::Platform;
use crate::model::world::World;
use java_reader::JvmStream;

View file

@ -1,11 +1,11 @@
use std::collections::HashMap;
use super::super::java_reader::JvmStream;
use super::java_types::{
ClassDesc, FieldInfo, JvmValue, BASE_WIRE_HANDLE, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG,
BASE_WIRE_HANDLE, ClassDesc, FieldInfo, JvmValue, TC_ARRAY, TC_BLOCKDATA, TC_BLOCKDATALONG,
TC_CLASS, TC_CLASSDESC, TC_ENDBLOCKDATA, TC_ENUM, TC_LONGSTRING, TC_NULL, TC_OBJECT,
TC_PROXYCLASSDESC, TC_REFERENCE, TC_RESET, TC_STRING,
};
use super::super::java_reader::JvmStream;
impl JvmStream {
pub fn read_classdesc(&mut self) -> std::io::Result<JvmValue> {
@ -124,10 +124,7 @@ impl JvmStream {
other => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!(
"unexpected token in class annotations: 0x{:02x}",
other
),
format!("unexpected token in class annotations: 0x{:02x}", other),
));
}
}
@ -182,5 +179,4 @@ impl JvmStream {
self.handles[(handle - BASE_WIRE_HANDLE) as usize] = obj.clone();
Ok(obj)
}
}

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use super::super::java_reader::JvmStream;
use super::java_types::{
ClassDesc, FieldInfo, JvmValue, PRIM_BOOL, PRIM_BYTE, PRIM_CHAR, PRIM_DOUBLE, PRIM_FLOAT,
PRIM_INT, PRIM_LONG, PRIM_SHORT, SC_WRITE_METHOD, TC_BLOCKDATA, TC_BLOCKDATALONG,
};
use super::super::java_reader::JvmStream;
impl JvmStream {
pub fn read_object_fields(&mut self, cd: &ClassDesc) -> std::io::Result<JvmValue> {

View file

@ -14,8 +14,8 @@ pub use coords::{BlockCoords, ChunkCoords, RegionCoords, TileCoords};
pub use io::export;
pub use io::import;
pub use model::dimension::Dimension;
pub use model::types::{Platform, Terrain};
pub use model::tile::Tile;
pub use model::types::{Platform, Terrain};
pub use model::world::World;
pub use terrafier_biome_db as biome_db;

View file

@ -20,7 +20,11 @@ impl World {
}
/// Create a new world using a custom height map source.
pub fn with_heightmap(name: &str, seed: u64, heightmap: impl HeightMapSource + 'static) -> Self {
pub fn with_heightmap(
name: &str,
seed: u64,
heightmap: impl HeightMapSource + 'static,
) -> Self {
let platform = Platform::java_1_18();
let coords: Vec<(i32, i32)> = (-1..=1)

View file

@ -1,9 +1,9 @@
//! Filter system — restrict which cells an operation affects based on terrain, height, or slope.
use std::sync::OnceLock;
use crate::model::dimension::Dimension;
use crate::model::tile::{TILE_SIZE, Tile};
use crate::model::types::Terrain;
use crate::model::tile::{Tile, TILE_SIZE};
use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation};
use std::sync::OnceLock;
/// Determines whether a cell should be modified by an operation.
pub trait Filter: Send + Sync {
fn name(&self) -> &'static str;

View file

@ -1,7 +1,7 @@
//! Height map sources — generate height maps from noise, flat, or combined sources.
use crate::model::types::Terrain;
use crate::model::tile::Tile;
use crate::model::types::Terrain;
/// A source that can generate a height map for a tile.
pub trait HeightMapSource: Send + Sync {

View file

@ -13,7 +13,9 @@ fn tile_hash(tile_x: i32, tile_z: i32, seed: u64) -> u64 {
}
fn pseudo_rand(state: &mut u64) -> u64 {
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
*state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
*state >> 33
}
@ -34,9 +36,7 @@ impl crate::ops::layers::LayerGenerator for TreesGenerator {
for idx in 0..total {
let terrain_type = terrain[idx] as usize;
bytes[idx] = match terrain_type {
t if t == Terrain::Forest as usize => {
(pseudo_rand(&mut rng) % 60).max(20) as u8
}
t if t == Terrain::Forest as usize => (pseudo_rand(&mut rng) % 60).max(20) as u8,
t if t == Terrain::Grass as usize && pseudo_rand(&mut rng) % 100 < 30 => {
(pseudo_rand(&mut rng) % 20 + 5) as u8
}

View file

@ -1,9 +1,9 @@
use std::sync::{Arc, OnceLock};
use crate::model::types::Brush;
use crate::model::dimension::Dimension;
use crate::model::types::Terrain;
use crate::model::tile::TILE_SIZE;
use crate::model::types::Brush;
use crate::model::types::Terrain;
use crate::ops::operations::{Operation, OperationError, RestoreTerrainOperation};
use std::sync::{Arc, OnceLock};
struct BrushApply<'a> {
tile: &'a mut crate::model::tile::Tile,

View file

@ -2,8 +2,8 @@ use std::collections::VecDeque;
use std::sync::OnceLock;
use crate::model::dimension::Dimension;
use crate::model::types::Terrain;
use crate::model::tile::TILE_SIZE;
use crate::model::types::Terrain;
use super::{Operation, OperationError, RestoreTerrainOperation};

View file

@ -18,12 +18,14 @@ pub enum OperationError {
}
mod brush;
mod shaping;
mod fill;
mod shaping;
pub use brush::{FloodOperation, PaintOperation, PencilOperation};
pub use shaping::{ErodeOperation, FlattenOperation, HeightOperation, MultiTileOperation, SmoothOperation};
pub use fill::FillOperation;
pub use shaping::{
ErodeOperation, FlattenOperation, HeightOperation, MultiTileOperation, SmoothOperation,
};
/// Restores heights from a saved snapshot (used as inverse of FlattenOperation).
pub struct RestoreHeightsOperation {

View file

@ -1,9 +1,9 @@
use std::sync::Arc;
use std::sync::OnceLock;
use crate::model::types::Brush;
use crate::model::dimension::Dimension;
use crate::model::tile::TILE_SIZE;
use crate::model::types::Brush;
use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation};

View file

@ -1,9 +1,9 @@
use std::sync::Arc;
use std::sync::OnceLock;
use crate::model::types::Brush;
use crate::model::dimension::Dimension;
use crate::model::tile::TILE_SIZE;
use crate::model::types::Brush;
use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation};

View file

@ -1,6 +1,6 @@
mod erode;
mod height;
mod smooth;
mod erode;
pub use erode::*;
pub use height::*;
pub use smooth::*;
pub use erode::*;

View file

@ -1,9 +1,9 @@
use std::sync::Arc;
use std::sync::OnceLock;
use crate::model::types::Brush;
use crate::model::dimension::Dimension;
use crate::model::tile::TILE_SIZE;
use crate::model::types::Brush;
use crate::ops::operations::{Operation, OperationError, RestoreHeightsOperation};

View file

@ -1,7 +1,7 @@
//! Plugin host — trait-объекты для расширения функциональности.
mod traits;
pub mod registry;
mod traits;
pub use traits::{ExportPlugin, LayerPlugin, OperationPlugin, TileSourcePlugin};
pub use registry::PluginRegistry;
pub use traits::{ExportPlugin, LayerPlugin, OperationPlugin, TileSourcePlugin};