fix(tiled): match default layer order and visibility
Place Objects above Ground Items and Triggers, with Floors and Wiring/Plumbing hidden by default. Apply the defaults to exports and newly created conversion layers while preserving existing layer visibility. Verify hidden layers still import gameplay entities. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
b0fa5b3ec4
commit
402aa12043
|
|
@ -5,6 +5,12 @@ based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Tiled exports and newly created conversion layers place Objects above Ground
|
||||
Items, Triggers, Wiring/Plumbing and Floors, with Floors and Wiring/Plumbing
|
||||
hidden by default in the editor without affecting gameplay.
|
||||
|
||||
### Added
|
||||
|
||||
- `cargo xtask watch --serve` serves the packaging output with directory listings
|
||||
|
|
|
|||
|
|
@ -421,6 +421,8 @@ Tiles to Objects**. The extension replaces all marked painted cells with selecte
|
|||
layers with those names are reused. The converter also creates **Wiring/Plumbing**,
|
||||
**Ground Items** and **Triggers** layers when missing. Select an object
|
||||
to edit its health, solidity or other custom properties in the Properties panel.
|
||||
New layers use the same order and visibility defaults as world exports below;
|
||||
existing layers retain their visibility settings.
|
||||
Unmarked terrain stays painted. Floor/structure overlaps are allowed; same-layer
|
||||
overlaps abort the entire conversion. Conversion is a single undoable operation,
|
||||
and running it again does not duplicate objects.
|
||||
|
|
@ -452,7 +454,11 @@ and coordinate conventions; conversion is optional before loading a map.
|
|||
Objects are organized into **Floors**, **Objects** (walls, machines and other
|
||||
structures), **Ground Items**, and **Triggers** (currently spawn points).
|
||||
**Wiring/Plumbing** is an empty organizational placeholder; it does not add
|
||||
wiring, plumbing or trigger mechanics. Hide or lock individual layers in Tiled
|
||||
wiring, plumbing or trigger mechanics. From top to bottom, Tiled shows
|
||||
**Objects**, **Ground Items**, **Triggers**, **Wiring/Plumbing**, **Floors**,
|
||||
then the painted terrain layers (for the default map, **Walls** above **Ground**).
|
||||
**Floors** and **Wiring/Plumbing** are hidden by default to reduce editor clutter;
|
||||
hidden layers still load into the game. Hide or lock individual layers in Tiled
|
||||
to edit one category without the others getting in the way. Object classes and
|
||||
properties determine gameplay, not the editor layer names.
|
||||
3. Run `/import` (or `/import FILE`). **Import replaces all terrain, buildings and
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ function stationConvert(map) {
|
|||
if (planned.length === 0)
|
||||
return 0;
|
||||
|
||||
const names = ["Floors", "Wiring/Plumbing", "Objects", "Ground Items", "Triggers"];
|
||||
const names = ["Floors", "Wiring/Plumbing", "Triggers", "Ground Items", "Objects"];
|
||||
const destinations = new Map();
|
||||
for (const name of names) {
|
||||
const matches = layers.filter(layer => layer.isObjectLayer && layer.name === name);
|
||||
|
|
@ -120,6 +120,7 @@ function stationConvert(map) {
|
|||
if (!destinations.has(name)) {
|
||||
const objects = new ObjectGroup();
|
||||
objects.name = name;
|
||||
objects.visible = name !== "Floors" && name !== "Wiring/Plumbing";
|
||||
map.addLayer(objects);
|
||||
destinations.set(name, objects);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,9 +447,9 @@ pub fn write(save: &SaveGame, path: &Path) -> Result<PathBuf, MapIoError> {
|
|||
for (index, name) in [
|
||||
"Floors",
|
||||
"Wiring/Plumbing",
|
||||
"Objects",
|
||||
"Ground Items",
|
||||
"Triggers",
|
||||
"Ground Items",
|
||||
"Objects",
|
||||
]
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
|
@ -461,6 +461,15 @@ pub fn write(save: &SaveGame, path: &Path) -> Result<PathBuf, MapIoError> {
|
|||
("id", (layout.layers.len() + index + 1).to_string()),
|
||||
("name", (*name).into()),
|
||||
("draworder", "topdown".into()),
|
||||
(
|
||||
"visible",
|
||||
if matches!(*name, "Floors" | "Wiring/Plumbing") {
|
||||
"0"
|
||||
} else {
|
||||
"1"
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
],
|
||||
false,
|
||||
)?;
|
||||
|
|
@ -579,7 +588,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn writer_uses_tmx_extension_and_separates_object_categories() {
|
||||
let (_, save) = seeded();
|
||||
let (data, save) = seeded();
|
||||
for extension in ["", "backup", "TMX", "tmx"] {
|
||||
let requested = path().with_extension(extension);
|
||||
let saved = write(&save, &requested).unwrap();
|
||||
|
|
@ -591,6 +600,7 @@ mod tests {
|
|||
let mut reader = quick_xml::Reader::from_file(&saved).unwrap();
|
||||
let mut buffer = Vec::new();
|
||||
let mut groups = BTreeMap::<String, usize>::new();
|
||||
let mut group_order = Vec::new();
|
||||
let mut current = None;
|
||||
let mut layer_ids = BTreeSet::new();
|
||||
let mut object_ids = BTreeSet::new();
|
||||
|
|
@ -598,6 +608,7 @@ mod tests {
|
|||
match reader.read_event_into(&mut buffer).unwrap() {
|
||||
Event::Start(element) if element.name().as_ref() == b"objectgroup" => {
|
||||
let mut name = None;
|
||||
let mut visible = None;
|
||||
for attribute in element.attributes() {
|
||||
let attribute = attribute.unwrap();
|
||||
let value = attribute
|
||||
|
|
@ -610,10 +621,20 @@ mod tests {
|
|||
match attribute.key.as_ref() {
|
||||
b"name" => name = Some(value),
|
||||
b"id" => assert!(layer_ids.insert(value)),
|
||||
b"visible" => visible = Some(value),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let name = name.unwrap();
|
||||
assert_eq!(
|
||||
visible.as_deref(),
|
||||
Some(if matches!(name.as_str(), "Floors" | "Wiring/Plumbing") {
|
||||
"0"
|
||||
} else {
|
||||
"1"
|
||||
})
|
||||
);
|
||||
group_order.push(name.clone());
|
||||
assert!(groups.insert(name.clone(), 0).is_none());
|
||||
current = Some(name);
|
||||
}
|
||||
|
|
@ -679,6 +700,20 @@ mod tests {
|
|||
.iter()
|
||||
.filter(|b| b.state.layer == game_core::data::BuildingLayer::Floor)
|
||||
.count();
|
||||
assert_eq!(
|
||||
group_order,
|
||||
[
|
||||
"Floors",
|
||||
"Wiring/Plumbing",
|
||||
"Triggers",
|
||||
"Ground Items",
|
||||
"Objects"
|
||||
]
|
||||
);
|
||||
let loaded =
|
||||
MapLayout::load(&game_core::assets_dir(), saved.to_str().unwrap()).unwrap();
|
||||
let imported = decode(loaded, &data).unwrap();
|
||||
assert_eq!(imported.buildings.len(), save.buildings.len());
|
||||
assert!(floors > 0);
|
||||
assert_eq!(
|
||||
groups,
|
||||
|
|
|
|||
|
|
@ -102,11 +102,13 @@ test("converts floors and walls together, retaining properties, flags and decora
|
|||
assert.equal(context.stationConvert(map), 2);
|
||||
assert.equal(map.macros, 1);
|
||||
assert.deepEqual(map.layers.slice(2).map(layer => layer.name),
|
||||
["Floors", "Wiring/Plumbing", "Objects", "Ground Items", "Triggers"]);
|
||||
const objects = [map.layers[2].objects[0], map.layers[4].objects[0]];
|
||||
["Floors", "Wiring/Plumbing", "Triggers", "Ground Items", "Objects"]);
|
||||
assert.deepEqual(map.layers.slice(2).map(layer => layer.visible),
|
||||
[false, false, true, true, true]);
|
||||
const objects = [map.layers[2].objects[0], map.layers[6].objects[0]];
|
||||
assert.equal(map.layers[2].objects.length, 1);
|
||||
assert.equal(map.layers[3].objects.length, 0);
|
||||
assert.equal(map.layers[4].objects.length, 1);
|
||||
assert.equal(map.layers[6].objects.length, 1);
|
||||
assert.equal(objects[0].className, "building");
|
||||
assert.equal(objects[0].shape, "point");
|
||||
assert.equal(objects[0].x, 16);
|
||||
|
|
@ -136,17 +138,21 @@ test("reuses categorized object layers and undoes additions without changing exi
|
|||
const {map, context} = harness();
|
||||
const floors = new context.ObjectGroup();
|
||||
floors.name = "Floors";
|
||||
floors.visible = true;
|
||||
const existing = {className: "building", x: 48, y: 48, tile: null,
|
||||
properties: () => ({proto: "floor", "state.layer": '"floor"'})};
|
||||
floors.addObject(existing);
|
||||
const objects = new context.ObjectGroup();
|
||||
objects.name = "Objects";
|
||||
objects.visible = false;
|
||||
map.layers.push(floors, objects, layer([[0, 0, tile({proto: "floor", "state.layer": '"floor"'})]]));
|
||||
assert.equal(context.stationConvert(map), 1);
|
||||
assert.equal(map.layers.filter(layer => layer.name === "Floors").length, 1);
|
||||
assert.equal(floors.objects.length, 2);
|
||||
assert.equal(floors.objects[0], existing);
|
||||
assert.equal(objects.objects.length, 0);
|
||||
assert.equal(floors.visible, true);
|
||||
assert.equal(objects.visible, false);
|
||||
map.undo();
|
||||
assert.equal(floors.objects.length, 1);
|
||||
assert.equal(floors.objects[0], existing);
|
||||
|
|
|
|||
Loading…
Reference in New Issue