Skip to content

Dimensions — 自定义维度

用 Rust 重新实现的 MoreDimensions。注册原版三个维度之外的新维度,id 从 3 开始。

需要开启 more_dimensions 特性(仅服务端):

toml
[dependencies]
levilamina = { version = "26.20.4", features = ["more_dimensions"] }

这个特性只是「入口」

C++ 侧是无条件编译进加载器的,启动时自行初始化,钩子和维度配置一直是活的。cargo 特性只决定 Rust 这边能不能调到它,不是开关is_available() 只当防御性探针用,别拿来当启用判断。

客户端怎么认这些维度

基岩版客户端只知道三个原版维度。加载器在 ChangeDimensionPacket 等几个包里把维度 id 在线路上改写成一个假的原版 id,客户端照常渲染,不会报错。

维度元数据存在 configs/levilamina-rust-loader/dimensions.json,所以 id 跨重启稳定。

注册

rust
use levilamina::more_dimensions::{self, GeneratorType, PlotLayout};

// 每次启动都无条件调用 —— 幂等,同名返回同一个 id
let id = more_dimensions::add_simple_dimension("skylands", 12345, GeneratorType::Overworld)?;
API说明
add_simple_dimension(name, seed, generator)普通维度,返回 Result<i32>
add_plot_dimension(name, seed, &layout)地皮维度,生成器直接铺出网格
get_dimension_id(name)名字 → id(也认原版名字
get_custom_dimension_id(name)只认自定义维度(>= 3)
is_available()防御性探针

GeneratorTypeOverworld=1Flat=2Nether=3TheEnd=4Void=5

不要拿 get_dimension_id 当「存在性检查」

rust
// ❌ 错的
if let Some(id) = get_dimension_id("myworld") { return id; }
let id = add_simple_dimension("myworld", 0, GeneratorType::Void)?;

get_dimension_id 回答的是"这个名字对应哪个 id",不是"我的维度建好没有",而且它连原版名字一起认。旧版桥接对未知名字返回 VanillaDimensions::Undefined(),于是有模组把整个世界布局挂到了维度 0——玩家的主世界上。

注册本身就是幂等的,无条件调用即可

rust
// ✅ 对的
let id = add_simple_dimension("myworld", 0, GeneratorType::Void)?;

何时注册

必须在关卡已经打开之后,也就是 on_enable 里或某个调度任务里。

on_load 阶段 Level 还是 null,注册会抛异常。也不要在后台线程调。

地皮维度

生成器在区块生成时直接铺出道路、边框和地板,所以世界是无限的,"建造"不花任何时间。

rust
use levilamina::more_dimensions::PlotLayout;

let layout = PlotLayout {
    plot_size: 32,
    road_width: 7,
    border_width: 1,
    floor_y: 64,
    floor_block: "minecraft:grass_block".into(),
    fill_block: "minecraft:dirt".into(),
    road_block: "minecraft:smooth_stone".into(),
    border_block: "minecraft:stone_bricks".into(),
    biome: "plains".into(),
};
let id = more_dimensions::add_plot_dimension("plots", 0, &layout)?;

网格约定

cell = plot_size + road_width,世界坐标 (x, z) 这一列是:

  • 道路x.rem_euclid(cell) >= plot_size || z.rem_euclid(cell) >= plot_size
  • 边框:在地皮边缘 border_width 格以内
  • 地皮内部:其余

也就是说地皮占每个格子的低位 [0, plot_size),道路占高位 [plot_size, cell),边框算在 plot_size 里面

你的归属判定必须用同一个公式

对不上的话,玩家看到的方块和他站着的那块地皮就不是一回事。

另外布局是跟着维度一起持久化的——后来改配置也不会改已建好的世界。改几何会让所有已有地皮错位,所以要换布局就新建一个世界。

按维度的行为规则

基岩版的 gamerule 是全服一份的:想让创造用的地皮世界不刷怪,只能 doMobSpawning=false,而这会把同一台服务器上的生存世界也变空。

这里的规则钩在真正干活的函数上(Spawner::spawnMob 等),那些函数带着 BlockSource,能拿到维度 id,所以真正做到按维度隔离。

rust
use levilamina::more_dimensions::{self, DimensionRule};

more_dimensions::set_dimension_rule(plot_dim, DimensionRule::SpawnMonster, false);
more_dimensions::set_dimension_rule(plot_dim, DimensionRule::ExplodeBlocks, false);
规则管什么
SpawnMonster0怪物自然生成
SpawnAnimal1动物自然生成
SpawnSpawner2刷怪笼
ExplodeBlocks3爆炸破坏方块
FireSpread4火焰蔓延
MobGriefing5生物破坏方块
Projectile6弹射物
PistonPush7活塞推动
LiquidFlow8液体流动
FarmlandDecay9耕地被踩坏
Ride10骑乘
PistonCrossPlot11活塞跨地皮推动
EntityCrossPlot12实体跨地皮移动
API说明
set_dimension_rule(dim, rule, allow)allow = false 表示禁止
get_dimension_rule(dim, rule)None = 没设过,走原版逻辑
clear_dimension_rules(dim)清掉一个维度的全部规则

没设过的维度完全不受影响

规则表是稀疏的,查不到就走原版。钩子虽然是全局装的,但"默认放行"是硬性约束——原版维度必须感觉不到它们存在。

地皮网格与合并

PistonCrossPlot / EntityCrossPlot 需要加载器知道网格几何,否则那两条规则恒等于放行。

rust
more_dimensions::set_plot_grid(dim, 32, 7);      // plot_size, road_width
more_dimensions::clear_plot_grid(dim);

合并(把相邻地皮打通)用整表替换:

rust
use levilamina::more_dimensions::PlotMerge;

let merges = vec![
    PlotMerge::from_dirs(0, 0, [false, true, false, false]),  // 0=北 1=东 2=南 3=西
];
more_dimensions::set_plot_merges(dim, &merges);

位掩码:NORTH=1(z-)、EAST=2(x+)、SOUTH=4(z+)、WEST=8(x-)。

只需要传真的有合并标记的地皮——没条目的按"四面都没合并"处理。几千块地皮、十来处合并的服务器,这张表也就几十个整数。

为什么是整表替换而不是增量

增量要求两边对"现在有哪些条目"的看法永远一致,而拆分是先清邻居、再存自己、中途可能失败的。一旦对不上,增量再也没有自愈的机会。整表替换每次都把状态拉回一致。

另外改了网格几何之后要重推一次 set_plot_merges——几何变化会清掉合并缓存。

注意维度 id 不再只有 0/1/2

一旦服务器上有自定义维度,player.dimension()actor.dimension_id()、事件载荷里的 dim 都可能是 >= 3 的值。

rust
// ❌ 会漏掉自定义维度
match dim { 0 => "主世界", 1 => "下界", _ => "末地" }

// ✅
match dim { 0 => "主世界", 1 => "下界", 2 => "末地", n => &format!("自定义 {n}") }