Editor: remove tiles from painted zones, scrollable zone list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 21:28:38 +01:00
1 parent a8d1c4d6ea
commit c11d281281
20 files changed
+91 -2

No files matched your search

+1 -1
View File
@@ -95,7 +95,7 @@ The map in the game's 3D view; tool bar on the left, options on top.
| Towers | arrow, cannon, magic, rune forge tower; the dungeon's or the knights', level 1–3 | | Towers | arrow, cannon, magic, rune forge tower; the dungeon's or the knights', level 1–3 |
| Traps | the seven traps and three doors, armed | | Traps | the seven traps and three doors, armed |
| Forts | a knight fortress (with or without a shard), rival masters (Mossgrim, Lady Nightwell, rival 3); level = strength | | Forts | a knight fortress (with or without a shard), rival masters (Mossgrim, Lady Nightwell, rival 3); level = strength |
| Zones | trigger zones: rectangle, circle (drag from the middle), painted tiles (paint onto the selected tiles zone), line (drag from start to end); name, rename, delete | | Zones | trigger zones: rectangle, circle (drag from the middle), painted tiles (paint onto the selected tiles zone; *Remove tiles* takes them out again), line (drag from start to end); name, rename, delete (the "x"); the list scrolls with the wheel |
| Erase | removes units, towers, traps and doors on a tile | | Erase | removes units, towers, traps and doors on a tile |
Options: brush size 1–5, rectangle, fill (connected area of the same ground), mirror (left/right, Options: brush size 1–5, rectangle, fill (connected area of the same ground), mirror (left/right,
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Úroveň
ed.zone_shape = Tvar nové zóny ed.zone_shape = Tvar nové zóny
ed.zone_new_name = Název nové zóny ed.zone_new_name = Název nové zóny
ed.zones = Zóny ed.zones = Zóny
ed.zone_erase = Odebrat pole
ed.shape.rect = Obdélník ed.shape.rect = Obdélník
ed.shape.circle = Kruh ed.shape.circle = Kruh
ed.shape.tiles = Políčka ed.shape.tiles = Políčka
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Niveau
ed.zone_shape = Form på ny zone ed.zone_shape = Form på ny zone
ed.zone_new_name = Navn på ny zone ed.zone_new_name = Navn på ny zone
ed.zones = Zoner ed.zones = Zoner
ed.zone_erase = Fjern felter
ed.shape.rect = Rektangel ed.shape.rect = Rektangel
ed.shape.circle = Cirkel ed.shape.circle = Cirkel
ed.shape.tiles = Felter ed.shape.tiles = Felter
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Stufe
ed.zone_shape = Form einer neuen Zone ed.zone_shape = Form einer neuen Zone
ed.zone_new_name = Name einer neuen Zone ed.zone_new_name = Name einer neuen Zone
ed.zones = Zonen ed.zones = Zonen
ed.zone_erase = Kacheln entfernen
ed.shape.rect = Rechteck ed.shape.rect = Rechteck
ed.shape.circle = Kreis ed.shape.circle = Kreis
ed.shape.tiles = Kacheln ed.shape.tiles = Kacheln
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Επίπεδο
ed.zone_shape = Σχήμα νέας ζώνης ed.zone_shape = Σχήμα νέας ζώνης
ed.zone_new_name = Όνομα νέας ζώνης ed.zone_new_name = Όνομα νέας ζώνης
ed.zones = Ζώνες ed.zones = Ζώνες
ed.zone_erase = Αφαίρεση πλακιδίων
ed.shape.rect = Ορθογώνιο ed.shape.rect = Ορθογώνιο
ed.shape.circle = Κύκλος ed.shape.circle = Κύκλος
ed.shape.tiles = Πλακίδια ed.shape.tiles = Πλακίδια
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Level
ed.zone_shape = Shape of a new zone ed.zone_shape = Shape of a new zone
ed.zone_new_name = Name of a new zone ed.zone_new_name = Name of a new zone
ed.zones = Zones ed.zones = Zones
ed.zone_erase = Remove tiles
ed.shape.rect = Rectangle ed.shape.rect = Rectangle
ed.shape.circle = Circle ed.shape.circle = Circle
ed.shape.tiles = Tiles ed.shape.tiles = Tiles
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Nivel
ed.zone_shape = Forma de la nueva zona ed.zone_shape = Forma de la nueva zona
ed.zone_new_name = Nombre de la nueva zona ed.zone_new_name = Nombre de la nueva zona
ed.zones = Zonas ed.zones = Zonas
ed.zone_erase = Quitar casillas
ed.shape.rect = Rectángulo ed.shape.rect = Rectángulo
ed.shape.circle = Círculo ed.shape.circle = Círculo
ed.shape.tiles = Casillas ed.shape.tiles = Casillas
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Taso
ed.zone_shape = Uuden alueen muoto ed.zone_shape = Uuden alueen muoto
ed.zone_new_name = Uuden alueen nimi ed.zone_new_name = Uuden alueen nimi
ed.zones = Alueet ed.zones = Alueet
ed.zone_erase = Poista ruutuja
ed.shape.rect = Suorakulmio ed.shape.rect = Suorakulmio
ed.shape.circle = Ympyrä ed.shape.circle = Ympyrä
ed.shape.tiles = Ruudut ed.shape.tiles = Ruudut
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Razina
ed.zone_shape = Oblik nove zone ed.zone_shape = Oblik nove zone
ed.zone_new_name = Ime nove zone ed.zone_new_name = Ime nove zone
ed.zones = Zone ed.zones = Zone
ed.zone_erase = Ukloni polja
ed.shape.rect = Pravokutnik ed.shape.rect = Pravokutnik
ed.shape.circle = Krug ed.shape.circle = Krug
ed.shape.tiles = Polja ed.shape.tiles = Polja
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Szint
ed.zone_shape = Új zóna alakja ed.zone_shape = Új zóna alakja
ed.zone_new_name = Új zóna neve ed.zone_new_name = Új zóna neve
ed.zones = Zónák ed.zones = Zónák
ed.zone_erase = Mezők törlése
ed.shape.rect = Téglalap ed.shape.rect = Téglalap
ed.shape.circle = Kör ed.shape.circle = Kör
ed.shape.tiles = Mezők ed.shape.tiles = Mezők
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Livello
ed.zone_shape = Forma della nuova zona ed.zone_shape = Forma della nuova zona
ed.zone_new_name = Nome della nuova zona ed.zone_new_name = Nome della nuova zona
ed.zones = Zone ed.zones = Zone
ed.zone_erase = Togli caselle
ed.shape.rect = Rettangolo ed.shape.rect = Rettangolo
ed.shape.circle = Cerchio ed.shape.circle = Cerchio
ed.shape.tiles = Caselle ed.shape.tiles = Caselle
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Niveau
ed.zone_shape = Vorm van een nieuwe zone ed.zone_shape = Vorm van een nieuwe zone
ed.zone_new_name = Naam van een nieuwe zone ed.zone_new_name = Naam van een nieuwe zone
ed.zones = Zones ed.zones = Zones
ed.zone_erase = Tegels weghalen
ed.shape.rect = Rechthoek ed.shape.rect = Rechthoek
ed.shape.circle = Cirkel ed.shape.circle = Cirkel
ed.shape.tiles = Vakken ed.shape.tiles = Vakken
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Nivå
ed.zone_shape = Form på ny sone ed.zone_shape = Form på ny sone
ed.zone_new_name = Navn på ny sone ed.zone_new_name = Navn på ny sone
ed.zones = Soner ed.zones = Soner
ed.zone_erase = Fjern ruter
ed.shape.rect = Rektangel ed.shape.rect = Rektangel
ed.shape.circle = Sirkel ed.shape.circle = Sirkel
ed.shape.tiles = Ruter ed.shape.tiles = Ruter
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nível
ed.zone_shape = Forma da nova zona ed.zone_shape = Forma da nova zona
ed.zone_new_name = Nome da nova zona ed.zone_new_name = Nome da nova zona
ed.zones = Zonas ed.zones = Zonas
ed.zone_erase = Remover casas
ed.shape.rect = Retângulo ed.shape.rect = Retângulo
ed.shape.circle = Círculo ed.shape.circle = Círculo
ed.shape.tiles = Casas ed.shape.tiles = Casas
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nivel
ed.zone_shape = Forma unei zone noi ed.zone_shape = Forma unei zone noi
ed.zone_new_name = Numele unei zone noi ed.zone_new_name = Numele unei zone noi
ed.zones = Zone ed.zones = Zone
ed.zone_erase = Scoate pătrate
ed.shape.rect = Dreptunghi ed.shape.rect = Dreptunghi
ed.shape.circle = Cerc ed.shape.circle = Cerc
ed.shape.tiles = Dale ed.shape.tiles = Dale
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Уровень
ed.zone_shape = Форма новой зоны ed.zone_shape = Форма новой зоны
ed.zone_new_name = Имя новой зоны ed.zone_new_name = Имя новой зоны
ed.zones = Зоны ed.zones = Зоны
ed.zone_erase = Убрать клетки
ed.shape.rect = Прямоугольник ed.shape.rect = Прямоугольник
ed.shape.circle = Круг ed.shape.circle = Круг
ed.shape.tiles = Клетки ed.shape.tiles = Клетки
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Úroveň
ed.zone_shape = Tvar novej zóny ed.zone_shape = Tvar novej zóny
ed.zone_new_name = Názov novej zóny ed.zone_new_name = Názov novej zóny
ed.zones = Zóny ed.zones = Zóny
ed.zone_erase = Odobrať polia
ed.shape.rect = Obdĺžnik ed.shape.rect = Obdĺžnik
ed.shape.circle = Kruh ed.shape.circle = Kruh
ed.shape.tiles = Políčka ed.shape.tiles = Políčka
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Ниво
ed.zone_shape = Облик нове зоне ed.zone_shape = Облик нове зоне
ed.zone_new_name = Име нове зоне ed.zone_new_name = Име нове зоне
ed.zones = Зоне ed.zones = Зоне
ed.zone_erase = Уклони поља
ed.shape.rect = Правоугаоник ed.shape.rect = Правоугаоник
ed.shape.circle = Круг ed.shape.circle = Круг
ed.shape.tiles = Поља ed.shape.tiles = Поља
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nivå
ed.zone_shape = Form på ny zon ed.zone_shape = Form på ny zon
ed.zone_new_name = Namn på ny zon ed.zone_new_name = Namn på ny zon
ed.zones = Zoner ed.zones = Zoner
ed.zone_erase = Ta bort rutor
ed.shape.rect = Rektangel ed.shape.rect = Rektangel
ed.shape.circle = Cirkel ed.shape.circle = Cirkel
ed.shape.tiles = Rutor ed.shape.tiles = Rutor
+72 -1
View File
@@ -61,6 +61,8 @@ pub struct MapEdit {
zone: usize, // selected zone zone: usize, // selected zone
zone_shape: usize, // shape for a new zone zone_shape: usize, // shape for a new zone
zone_name: String, zone_name: String,
zone_erase: bool, // painting a tiles zone takes tiles out of it again
zone_scroll: usize, // first zone shown in the list (usize::MAX: scroll to the end)
last_mouse: Option<Vec2>, last_mouse: Option<Vec2>,
} }
@@ -117,6 +119,8 @@ impl MapEdit {
zone: 0, zone: 0,
zone_shape: 0, zone_shape: 0,
zone_name: String::new(), zone_name: String::new(),
zone_erase: false,
zone_scroll: 0,
last_mouse: None, last_mouse: None,
} }
} }
@@ -460,9 +464,18 @@ impl MapEdit {
} }
m.zones.push(z); m.zones.push(z);
self.zone = m.zones.len() - 1; self.zone = m.zones.len() - 1;
self.zone_scroll = usize::MAX;
self.zone_name.clear(); self.zone_name.clear();
} }
// the brush takes tiles out of the selected tiles zone
fn erase_zone_tiles(&mut self, m: &mut MissionDoc, x: i32, y: i32) {
let targets = self.targets(x, y);
if let Some(t) = m.zones.get_mut(self.zone).filter(|z| z.shape == "tiles").and_then(|z| z.tiles.as_mut()) {
t.retain(|p| !targets.contains(p));
}
}
pub fn frame(&mut self, im: &mut Im, gfx: &Gfx, r3d: &mut R3d, dt: f32, now: f64, m: &mut MissionDoc) -> MapResult { pub fn frame(&mut self, im: &mut Im, gfx: &Gfx, r3d: &mut R3d, dt: f32, now: f64, m: &mut MissionDoc) -> MapResult {
let (cssx, cssy) = mouse_position(); let (cssx, cssy) = mouse_position();
let bar_w = CW() * 30.0; let bar_w = CW() * 30.0;
@@ -547,6 +560,15 @@ impl MapEdit {
} }
im.pressed = false; im.pressed = false;
} }
// a tiles zone is painted (or erased) all along the drag
let tiles_zone = self.cat == Cat::Zones && ZONE_SHAPES[self.zone_shape] == "tiles";
if tiles_zone && im.down && self.drag.is_some() {
if self.zone_erase {
self.erase_zone_tiles(m, x, y);
} else {
self.draw_zone(m, x, y, x, y);
}
}
if self.stroke && im.down { if self.stroke && im.down {
for (tx, ty) in self.targets(x, y) { for (tx, ty) in self.targets(x, y) {
self.paint_tile(tx, ty); self.paint_tile(tx, ty);
@@ -555,7 +577,11 @@ impl MapEdit {
if im.released { if im.released {
if let Some((sx, sy)) = self.drag { if let Some((sx, sy)) = self.drag {
if self.cat == Cat::Zones { if self.cat == Cat::Zones {
if ZONE_SHAPES[self.zone_shape] == "tiles" && self.zone_erase {
self.erase_zone_tiles(m, x, y);
} else {
self.draw_zone(m, sx, sy, x, y); self.draw_zone(m, sx, sy, x, y);
}
} else { } else {
for (tx, ty) in self.targets(x, y) { for (tx, ty) in self.targets(x, y) {
self.paint_tile(tx, ty); self.paint_tile(tx, ty);
@@ -738,7 +764,24 @@ impl MapEdit {
label(gfx, x, y, &tr("ed.zones"), FRAME_HI); label(gfx, x, y, &tr("ed.zones"), FRAME_HI);
y += CH() * 1.4; y += CH() * 1.4;
let mut remove = None; let mut remove = None;
for k in 0..m.zones.len() { // the list scrolls (wheel over it) when there are more zones than room
let row = CH() * 1.7;
let room = (((h - CH() * 3.0 - y) / row).floor() as usize).max(1);
let over_list = im.mx < bar_w && im.my >= y && im.my < y + room as f32 * row;
if over_list && im.wheel != 0.0 {
self.zone_scroll = if im.wheel > 0.0 { self.zone_scroll.min(m.zones.len()).saturating_sub(1) } else { self.zone_scroll.saturating_add(1) };
}
let max_scroll = m.zones.len().saturating_sub(room);
if self.zone_scroll == usize::MAX {
// a new zone: show it
self.zone_scroll = self.zone.saturating_sub(room - 1);
}
self.zone_scroll = self.zone_scroll.min(max_scroll);
if self.zone_scroll > 0 || max_scroll > 0 {
let more = format!("{}-{} / {}", self.zone_scroll + 1, (self.zone_scroll + room).min(m.zones.len()), m.zones.len());
gfx.text(&more, x + fw - gfx.measure(&more, SMALL()), y - CH() * 1.3, SMALL(), DIM);
}
for k in self.zone_scroll..m.zones.len() {
if y > h - CH() * 3.0 { if y > h - CH() * 3.0 {
break; break;
} }
@@ -778,6 +821,10 @@ impl MapEdit {
} }
bx += gfx.measure(&tr("ed.fill"), S()) + CW() * 4.0; bx += gfx.measure(&tr("ed.fill"), S()) + CW() * 4.0;
} }
if self.cat == Cat::Zones && ZONE_SHAPES[self.zone_shape] == "tiles" {
im.toggle(gfx, bx, by + CH() * 0.15, &tr("ed.zone_erase"), &mut self.zone_erase);
bx += gfx.measure(&tr("ed.zone_erase"), S()) + CW() * 4.0;
}
let mirrors: Vec<String> = (0..4).map(|k| tr(&format!("ed.mirror{}", k))).collect(); let mirrors: Vec<String> = (0..4).map(|k| tr(&format!("ed.mirror{}", k))).collect();
if let Some(c) = im.dropdown(gfx, id("mirror", 0), Rect::new(bx, by, CW() * 23.0, CH() * 1.7), &mirrors[self.mirror as usize], &mirrors) { if let Some(c) = im.dropdown(gfx, id("mirror", 0), Rect::new(bx, by, CW() * 23.0, CH() * 1.7), &mirrors[self.mirror as usize], &mirrors) {
self.mirror = c as u8; self.mirror = c as u8;
@@ -799,3 +846,27 @@ impl MapEdit {
done done
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tiles_of_a_painted_zone_can_be_taken_out_again() {
let mut ed = MapEdit::new(Game::new(3, 64, 64));
let mut m = MissionDoc::default();
ed.zone_shape = ZONE_SHAPES.iter().position(|s| *s == "tiles").unwrap();
ed.brush = 2;
ed.draw_zone(&mut m, 10, 10, 10, 10);
ed.draw_zone(&mut m, 14, 10, 14, 10);
assert_eq!(m.zones.len(), 1, "painting goes on in the selected tiles zone");
let n = m.zones[0].tiles.as_ref().unwrap().len();
assert!(n >= 2);
ed.erase_zone_tiles(&mut m, 10, 10);
let t = m.zones[0].tiles.as_ref().unwrap();
assert!(!t.contains(&(10, 10)) && t.contains(&(14, 10)));
assert!(t.len() < n);
// a new zone scrolls the list to it
assert_eq!(ed.zone_scroll, usize::MAX);
}
}