mirror of
https://github.com/louis-e/arnis.git
synced 2026-04-23 07:32:00 -04:00
Merge pull request #901 from luigi052005/better-traffic-lights
Better traffic lights
This commit is contained in:
@@ -491,12 +491,23 @@ pub fn to_bedrock_block(block: Block) -> BedrockBlock {
|
||||
),
|
||||
// Plain terracotta
|
||||
"terracotta" => BedrockBlock::simple("hardened_clay"),
|
||||
|
||||
// Wall banner — Bedrock uses "minecraft:wall_banner" with a
|
||||
// "facing_direction" int state: 2=north, 3=south, 4=west, 5=east.
|
||||
// The color is stored in the block entity (Base field), not the block state.
|
||||
// The facing string→int mapping is handled by to_bedrock_block_with_properties.
|
||||
"light_gray_wall_banner" => BedrockBlock::with_states(
|
||||
"wall_banner",
|
||||
vec![("facing_direction", BedrockBlockStateValue::Int(2))], // default north
|
||||
),
|
||||
// Wool colors
|
||||
"white_wool" => BedrockBlock::with_states(
|
||||
"wool",
|
||||
vec![("color", BedrockBlockStateValue::String("white".to_string()))],
|
||||
),
|
||||
"black_wool" => BedrockBlock::with_states(
|
||||
"wool",
|
||||
vec![("color", BedrockBlockStateValue::String("black".to_string()))],
|
||||
),
|
||||
"red_wool" => BedrockBlock::with_states(
|
||||
"wool",
|
||||
vec![("color", BedrockBlockStateValue::String("red".to_string()))],
|
||||
@@ -821,6 +832,11 @@ pub fn to_bedrock_block_with_properties(
|
||||
return convert_rail(props_map);
|
||||
}
|
||||
|
||||
// Handle wall banners with facing property
|
||||
if java_name == "light_gray_wall_banner" {
|
||||
return convert_wall_banner(props_map);
|
||||
}
|
||||
|
||||
// Fall back to basic conversion without properties
|
||||
to_bedrock_block(block)
|
||||
}
|
||||
@@ -1247,6 +1263,40 @@ fn convert_bed(
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert Java wall banner to Bedrock format.
|
||||
///
|
||||
/// Java stores facing as a string ("north"/"south"/"east"/"west") on the block state.
|
||||
/// Bedrock uses `facing_direction` as an integer on `minecraft:wall_banner`:
|
||||
/// 2 = north, 3 = south, 4 = west, 5 = east
|
||||
///
|
||||
/// The banner color (light_gray = 7) and patterns live in the block entity, not here.
|
||||
fn convert_wall_banner(
|
||||
props: Option<&std::collections::HashMap<String, fastnbt::Value>>,
|
||||
) -> BedrockBlock {
|
||||
let facing_direction = props
|
||||
.and_then(|p| p.get("facing"))
|
||||
.and_then(|v| match v {
|
||||
fastnbt::Value::String(s) => Some(s.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.map(|f| match f {
|
||||
"north" => 2,
|
||||
"south" => 3,
|
||||
"west" => 4,
|
||||
"east" => 5,
|
||||
_ => 2, // default north
|
||||
})
|
||||
.unwrap_or(2);
|
||||
|
||||
BedrockBlock::with_states(
|
||||
"wall_banner",
|
||||
vec![(
|
||||
"facing_direction",
|
||||
BedrockBlockStateValue::Int(facing_direction),
|
||||
)],
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert Java rail to Bedrock format with rail_direction from shape property.
|
||||
///
|
||||
/// Java uses `shape` strings ("north_south", "east_west", "ascending_east", etc.)
|
||||
|
||||
@@ -332,6 +332,8 @@ impl Block {
|
||||
251 => "cactus",
|
||||
252 => "gray_concrete_powder",
|
||||
253 => "cyan_terracotta",
|
||||
254 => "black_wool",
|
||||
255 => "light_gray_wall_banner",
|
||||
_ => panic!("Invalid id"),
|
||||
}
|
||||
}
|
||||
@@ -980,6 +982,8 @@ pub const RED_SANDSTONE: Block = Block::new(250);
|
||||
pub const CACTUS: Block = Block::new(251);
|
||||
pub const GRAY_CONCRETE_POWDER: Block = Block::new(252);
|
||||
pub const CYAN_TERRACOTTA: Block = Block::new(253);
|
||||
pub const BLACK_WOOL: Block = Block::new(254);
|
||||
pub const LIGHT_GRAY_WALL_BANNER: Block = Block::new(255);
|
||||
|
||||
/// Maps a block to its corresponding stair variant
|
||||
#[inline]
|
||||
|
||||
@@ -114,13 +114,43 @@ fn generate_highways_internal(
|
||||
let x: i32 = node.x;
|
||||
let z: i32 = node.z;
|
||||
|
||||
for dy in 1..=3 {
|
||||
editor.set_block(COBBLESTONE_WALL, x, dy, z, None, None);
|
||||
}
|
||||
// Traffic light blocks
|
||||
editor.set_block(COBBLESTONE_WALL, x, 1, z, None, None);
|
||||
editor.set_block(IRON_BARS, x, 2, z, None, None);
|
||||
editor.set_block(IRON_BARS, x, 3, z, None, None);
|
||||
editor.set_block(BLACK_WOOL, x, 4, z, None, None);
|
||||
editor.set_block(BLACK_WOOL, x, 5, z, None, None);
|
||||
|
||||
editor.set_block(GREEN_WOOL, x, 4, z, None, None);
|
||||
editor.set_block(YELLOW_WOOL, x, 5, z, None, None);
|
||||
editor.set_block(RED_WOOL, x, 6, z, None, None);
|
||||
// Banner placement logic
|
||||
let abs_y = editor.get_absolute_y(x, 5, z);
|
||||
let banner_offsets: [(i32, i32, &str); 4] = [
|
||||
(0, -1, "north"),
|
||||
(0, 1, "south"),
|
||||
(-1, 0, "west"),
|
||||
(1, 0, "east"),
|
||||
];
|
||||
|
||||
// patterns expressed as (java_color, java_pattern_id) pairs
|
||||
// so both Java and Bedrock writers can use them.
|
||||
const BANNER_PATTERNS: &[(&str, &str)] = &[
|
||||
("red", "minecraft:triangle_top"),
|
||||
("lime", "minecraft:triangle_bottom"),
|
||||
("yellow", "minecraft:circle"),
|
||||
("black", "minecraft:curly_border"),
|
||||
("black", "minecraft:border"),
|
||||
];
|
||||
|
||||
for (dx, dz, facing) in &banner_offsets {
|
||||
editor.place_wall_banner(
|
||||
LIGHT_GRAY_WALL_BANNER,
|
||||
x + dx,
|
||||
abs_y,
|
||||
z + dz,
|
||||
facing,
|
||||
"light_gray",
|
||||
BANNER_PATTERNS,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,17 @@ impl BedrockWriter {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data = nbtx::to_le_bytes(&deduped).map_err(|e| BedrockSaveError::Nbt(e.to_string()))?;
|
||||
// Bedrock block entities and entities are stored as CONCATENATED individual
|
||||
// NBT compounds — NOT as a single NBT list. Each compound is serialised
|
||||
// back-to-back with no wrapper. nbtx::to_le_bytes() on a Vec would produce
|
||||
// a TAG_List header, which Bedrock cannot parse.
|
||||
let mut data: Vec<u8> = Vec::new();
|
||||
for compound in &deduped {
|
||||
let bytes =
|
||||
nbtx::to_le_bytes(compound).map_err(|e| BedrockSaveError::Nbt(e.to_string()))?;
|
||||
data.extend_from_slice(&bytes);
|
||||
}
|
||||
|
||||
let key = build_chunk_key_bytes(chunk_pos, Dimension::Overworld, key_type, None);
|
||||
db.put(&key, &data)
|
||||
.map_err(|e| BedrockSaveError::Database(format!("{:?}", e)))?;
|
||||
|
||||
@@ -236,6 +236,223 @@ impl<'a> WorldEditor<'a> {
|
||||
let absolute_y = self.get_absolute_y(x, y, z);
|
||||
self.world.get_block(x, absolute_y, z).is_some()
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn place_wall_banner(
|
||||
&mut self,
|
||||
block: Block,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
facing: &str, // "north" / "south" / "east" / "west"
|
||||
base_color: &str, // "light_gray" etc.
|
||||
patterns: &[(&str, &str)], // [("red", "minecraft:triangle_top"), ...]
|
||||
) {
|
||||
// Apply Block rotation
|
||||
self.set_block_with_properties_absolute(
|
||||
crate::block_definitions::BlockWithProperties::new(
|
||||
block,
|
||||
Some(fastnbt::nbt!({ "facing": facing })),
|
||||
),
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
match self.format() {
|
||||
crate::world_editor::WorldFormat::JavaAnvil => {
|
||||
self.set_banner_block_entity_absolute(x, y, z, patterns);
|
||||
}
|
||||
crate::world_editor::WorldFormat::BedrockMcWorld => {
|
||||
self.set_bedrock_banner_block_entity_absolute(x, y, z, base_color, patterns);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_block_entity(&mut self, x: i32, z: i32, be: HashMap<String, Value>) {
|
||||
if !self.xzbbox.contains(&XZPoint::new(x, z)) {
|
||||
return;
|
||||
}
|
||||
let chunk_x = x >> 4;
|
||||
let chunk_z = z >> 4;
|
||||
let region_x = chunk_x >> 5;
|
||||
let region_z = chunk_z >> 5;
|
||||
|
||||
let region = self.world.get_or_create_region(region_x, region_z);
|
||||
let chunk = region.get_or_create_chunk(chunk_x & 31, chunk_z & 31);
|
||||
|
||||
const BLOCK_ENTITIES_KEY: &str = "block_entities";
|
||||
|
||||
match chunk.other.entry(BLOCK_ENTITIES_KEY.to_string()) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
if let Value::List(list) = entry.get_mut() {
|
||||
list.push(Value::Compound(be));
|
||||
}
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Value::List(vec![Value::Compound(be)]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Places a banner block entity at the given coordinates (absolute Y).
|
||||
/// This writes the pattern data into the chunk's block_entities list,
|
||||
/// which is required for the banner patterns to appear in-game.
|
||||
fn set_banner_block_entity_absolute(
|
||||
&mut self,
|
||||
x: i32,
|
||||
absolute_y: i32,
|
||||
z: i32,
|
||||
patterns_list: &[(&str, &str)],
|
||||
) {
|
||||
let mut be = HashMap::new();
|
||||
be.insert(
|
||||
"id".to_string(),
|
||||
Value::String("minecraft:banner".to_string()),
|
||||
);
|
||||
be.insert("x".to_string(), Value::Int(x));
|
||||
be.insert("y".to_string(), Value::Int(absolute_y));
|
||||
be.insert("z".to_string(), Value::Int(z));
|
||||
be.insert("keepPacked".to_string(), Value::Byte(0));
|
||||
let patterns: Vec<Value> = patterns_list
|
||||
.iter()
|
||||
.map(|(color, pattern)| {
|
||||
let mut entry = HashMap::new();
|
||||
|
||||
entry.insert("color".to_string(), Value::String(color.to_string()));
|
||||
entry.insert("pattern".to_string(), Value::String(pattern.to_string()));
|
||||
Value::Compound(entry)
|
||||
})
|
||||
.collect();
|
||||
be.insert("patterns".to_string(), Value::List(patterns));
|
||||
be.insert("components".to_string(), Value::Compound(HashMap::new()));
|
||||
self.insert_block_entity(x, z, be);
|
||||
}
|
||||
|
||||
/// Places a Bedrock-format banner block entity at the given coordinates (absolute Y).
|
||||
///
|
||||
/// Bedrock banners use a completely different block entity schema from Java:
|
||||
/// - `Base`: Int — base color index (0=black … 15=white; light_gray=7)
|
||||
/// - `Patterns`: List — each entry has `Color` (Int) and `Pattern` (String, short code)
|
||||
/// - `Type`: Int — 0 = normal banner
|
||||
///
|
||||
/// Java color names and pattern resource-path IDs are converted here to their
|
||||
/// Bedrock integer color indices and short pattern codes.
|
||||
fn set_bedrock_banner_block_entity_absolute(
|
||||
&mut self,
|
||||
x: i32,
|
||||
absolute_y: i32,
|
||||
z: i32,
|
||||
base_color: &str,
|
||||
patterns: &[(&str, &str)], // &[(java_color_name, java_pattern_id)]
|
||||
) {
|
||||
/// Maps a Java color name to the Bedrock integer color index used in banner
|
||||
/// block entities. The ordering is the standard Minecraft dye index.
|
||||
fn java_color_to_bedrock_int(color: &str) -> i32 {
|
||||
match color {
|
||||
"black" => 0,
|
||||
"red" => 1,
|
||||
"green" => 2,
|
||||
"brown" => 3,
|
||||
"blue" => 4,
|
||||
"purple" => 5,
|
||||
"cyan" => 6,
|
||||
"light_gray" => 7,
|
||||
"gray" => 8,
|
||||
"pink" => 9,
|
||||
"lime" => 10,
|
||||
"yellow" => 11,
|
||||
"light_blue" => 12,
|
||||
"magenta" => 13,
|
||||
"orange" => 14,
|
||||
"white" => 15,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps a Java banner pattern resource-path ID (e.g. "minecraft:triangle_top")
|
||||
/// to the Bedrock short pattern code (e.g. "tts").
|
||||
fn java_pattern_to_bedrock_code(pattern: &str) -> &'static str {
|
||||
// Strip the optional "minecraft:" namespace prefix
|
||||
let key = pattern.strip_prefix("minecraft:").unwrap_or(pattern);
|
||||
match key {
|
||||
"base" => "b",
|
||||
"square_bottom_left" => "bl",
|
||||
"square_bottom_right" => "br",
|
||||
"square_top_left" => "tl",
|
||||
"square_top_right" => "tr",
|
||||
"stripe_bottom" => "bs",
|
||||
"stripe_top" => "ts",
|
||||
"stripe_left" => "ls",
|
||||
"stripe_right" => "rs",
|
||||
"stripe_center" => "cs",
|
||||
"stripe_middle" => "ms",
|
||||
"stripe_downright" => "drs",
|
||||
"stripe_downleft" => "dls",
|
||||
"stripe_small" => "ss",
|
||||
"cross" => "cr",
|
||||
"straight_cross" => "sc",
|
||||
"triangle_bottom" => "bt",
|
||||
"triangle_top" => "tt",
|
||||
"triangles_bottom" => "bts",
|
||||
"triangles_top" => "tts",
|
||||
"diagonal_left" => "ld",
|
||||
"diagonal_right" => "rd",
|
||||
"diagonal_up_left" => "lud",
|
||||
"diagonal_up_right" => "rud",
|
||||
"circle" => "mc",
|
||||
"rhombus" => "mr",
|
||||
"half_vertical" => "vh",
|
||||
"half_vertical_right" => "vhr",
|
||||
"half_horizontal" => "hh",
|
||||
"half_horizontal_bottom" => "hhb",
|
||||
"border" => "bo",
|
||||
"curly_border" => "cbo",
|
||||
"gradient" => "gra",
|
||||
"gradient_up" => "gru",
|
||||
"bricks" => "bri",
|
||||
"globe" => "glb",
|
||||
"creeper" => "cre",
|
||||
"skull" => "sku",
|
||||
"flower" => "flo",
|
||||
"mojang" => "moj",
|
||||
"piglin" => "pig",
|
||||
"flow" => "flw",
|
||||
"guster" => "gus",
|
||||
_ => "b", // fallback: solid base
|
||||
}
|
||||
}
|
||||
|
||||
let bedrock_patterns: Vec<Value> = patterns
|
||||
.iter()
|
||||
.map(|(color, pattern)| {
|
||||
let mut entry = HashMap::new();
|
||||
entry.insert(
|
||||
"Color".to_string(),
|
||||
Value::Int(java_color_to_bedrock_int(color)),
|
||||
);
|
||||
entry.insert(
|
||||
"Pattern".to_string(),
|
||||
Value::String(java_pattern_to_bedrock_code(pattern).to_string()),
|
||||
);
|
||||
Value::Compound(entry)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut be = HashMap::new();
|
||||
be.insert("id".to_string(), Value::String("Banner".to_string()));
|
||||
be.insert("x".to_string(), Value::Int(x));
|
||||
be.insert("y".to_string(), Value::Int(absolute_y));
|
||||
be.insert("z".to_string(), Value::Int(z));
|
||||
be.insert(
|
||||
"Base".to_string(),
|
||||
Value::Int(java_color_to_bedrock_int(base_color)),
|
||||
);
|
||||
be.insert("Patterns".to_string(), Value::List(bedrock_patterns));
|
||||
be.insert("Type".to_string(), Value::Int(0));
|
||||
|
||||
self.insert_block_entity(x, z, be);
|
||||
}
|
||||
|
||||
/// Sets a sign at the given coordinates
|
||||
#[allow(clippy::too_many_arguments, dead_code)]
|
||||
|
||||
Reference in New Issue
Block a user