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
+92 -3

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 |
| 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 |
| 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 |
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_new_name = Název nové zóny
ed.zones = Zóny
ed.zone_erase = Odebrat pole
ed.shape.rect = Obdélník
ed.shape.circle = Kruh
ed.shape.tiles = Políčka
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Niveau
ed.zone_shape = Form på ny zone
ed.zone_new_name = Navn på ny zone
ed.zones = Zoner
ed.zone_erase = Fjern felter
ed.shape.rect = Rektangel
ed.shape.circle = Cirkel
ed.shape.tiles = Felter
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Stufe
ed.zone_shape = Form einer neuen Zone
ed.zone_new_name = Name einer neuen Zone
ed.zones = Zonen
ed.zone_erase = Kacheln entfernen
ed.shape.rect = Rechteck
ed.shape.circle = Kreis
ed.shape.tiles = Kacheln
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Επίπεδο
ed.zone_shape = Σχήμα νέας ζώνης
ed.zone_new_name = Όνομα νέας ζώνης
ed.zones = Ζώνες
ed.zone_erase = Αφαίρεση πλακιδίων
ed.shape.rect = Ορθογώνιο
ed.shape.circle = Κύκλος
ed.shape.tiles = Πλακίδια
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Level
ed.zone_shape = Shape of a new zone
ed.zone_new_name = Name of a new zone
ed.zones = Zones
ed.zone_erase = Remove tiles
ed.shape.rect = Rectangle
ed.shape.circle = Circle
ed.shape.tiles = Tiles
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Nivel
ed.zone_shape = Forma de la nueva zona
ed.zone_new_name = Nombre de la nueva zona
ed.zones = Zonas
ed.zone_erase = Quitar casillas
ed.shape.rect = Rectángulo
ed.shape.circle = Círculo
ed.shape.tiles = Casillas
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Taso
ed.zone_shape = Uuden alueen muoto
ed.zone_new_name = Uuden alueen nimi
ed.zones = Alueet
ed.zone_erase = Poista ruutuja
ed.shape.rect = Suorakulmio
ed.shape.circle = Ympyrä
ed.shape.tiles = Ruudut
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Razina
ed.zone_shape = Oblik nove zone
ed.zone_new_name = Ime nove zone
ed.zones = Zone
ed.zone_erase = Ukloni polja
ed.shape.rect = Pravokutnik
ed.shape.circle = Krug
ed.shape.tiles = Polja
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Szint
ed.zone_shape = Új zóna alakja
ed.zone_new_name = Új zóna neve
ed.zones = Zónák
ed.zone_erase = Mezők törlése
ed.shape.rect = Téglalap
ed.shape.circle = Kör
ed.shape.tiles = Mezők
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Livello
ed.zone_shape = Forma della nuova zona
ed.zone_new_name = Nome della nuova zona
ed.zones = Zone
ed.zone_erase = Togli caselle
ed.shape.rect = Rettangolo
ed.shape.circle = Cerchio
ed.shape.tiles = Caselle
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Niveau
ed.zone_shape = Vorm van een nieuwe zone
ed.zone_new_name = Naam van een nieuwe zone
ed.zones = Zones
ed.zone_erase = Tegels weghalen
ed.shape.rect = Rechthoek
ed.shape.circle = Cirkel
ed.shape.tiles = Vakken
+1
View File
@@ -1204,6 +1204,7 @@ ed.level = Nivå
ed.zone_shape = Form på ny sone
ed.zone_new_name = Navn på ny sone
ed.zones = Soner
ed.zone_erase = Fjern ruter
ed.shape.rect = Rektangel
ed.shape.circle = Sirkel
ed.shape.tiles = Ruter
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nível
ed.zone_shape = Forma da nova zona
ed.zone_new_name = Nome da nova zona
ed.zones = Zonas
ed.zone_erase = Remover casas
ed.shape.rect = Retângulo
ed.shape.circle = Círculo
ed.shape.tiles = Casas
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nivel
ed.zone_shape = Forma unei zone noi
ed.zone_new_name = Numele unei zone noi
ed.zones = Zone
ed.zone_erase = Scoate pătrate
ed.shape.rect = Dreptunghi
ed.shape.circle = Cerc
ed.shape.tiles = Dale
+1
View File
@@ -1197,6 +1197,7 @@ ed.level = Уровень
ed.zone_shape = Форма новой зоны
ed.zone_new_name = Имя новой зоны
ed.zones = Зоны
ed.zone_erase = Убрать клетки
ed.shape.rect = Прямоугольник
ed.shape.circle = Круг
ed.shape.tiles = Клетки
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Úroveň
ed.zone_shape = Tvar novej zóny
ed.zone_new_name = Názov novej zóny
ed.zones = Zóny
ed.zone_erase = Odobrať polia
ed.shape.rect = Obdĺžnik
ed.shape.circle = Kruh
ed.shape.tiles = Políčka
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Ниво
ed.zone_shape = Облик нове зоне
ed.zone_new_name = Име нове зоне
ed.zones = Зоне
ed.zone_erase = Уклони поља
ed.shape.rect = Правоугаоник
ed.shape.circle = Круг
ed.shape.tiles = Поља
+1
View File
@@ -1205,6 +1205,7 @@ ed.level = Nivå
ed.zone_shape = Form på ny zon
ed.zone_new_name = Namn på ny zon
ed.zones = Zoner
ed.zone_erase = Ta bort rutor
ed.shape.rect = Rektangel
ed.shape.circle = Cirkel
ed.shape.tiles = Rutor
+73 -2
View File
@@ -61,6 +61,8 @@ pub struct MapEdit {
zone: usize, // selected zone
zone_shape: usize, // shape for a new zone
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>,
}
@@ -117,6 +119,8 @@ impl MapEdit {
zone: 0,
zone_shape: 0,
zone_name: String::new(),
zone_erase: false,
zone_scroll: 0,
last_mouse: None,
}
}
@@ -460,9 +464,18 @@ impl MapEdit {
}
m.zones.push(z);
self.zone = m.zones.len() - 1;
self.zone_scroll = usize::MAX;
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 {
let (cssx, cssy) = mouse_position();
let bar_w = CW() * 30.0;
@@ -547,6 +560,15 @@ impl MapEdit {
}
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 {
for (tx, ty) in self.targets(x, y) {
self.paint_tile(tx, ty);
@@ -555,7 +577,11 @@ impl MapEdit {
if im.released {
if let Some((sx, sy)) = self.drag {
if self.cat == Cat::Zones {
self.draw_zone(m, sx, sy, x, y);
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);
}
} else {
for (tx, ty) in self.targets(x, y) {
self.paint_tile(tx, ty);
@@ -738,7 +764,24 @@ impl MapEdit {
label(gfx, x, y, &tr("ed.zones"), FRAME_HI);
y += CH() * 1.4;
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 {
break;
}
@@ -778,6 +821,10 @@ impl MapEdit {
}
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();
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;
@@ -799,3 +846,27 @@ impl MapEdit {
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);
}
}