diff --git a/lang/de.lang b/lang/de.lang index d3a10a9..7454394 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -1302,7 +1302,7 @@ blk.no = nein blk.on = an blk.off = aus blk.place_core = dem Kern -blk.place_event = der auslösenden Einheit +blk.place_event = dem Auslöser blk.place_zone = Zone {0} demo.name = Demo: alle Trigger demo.branch = Reich genug für einen Umweg diff --git a/lang/en.lang b/lang/en.lang index 4bbd642..8a970be 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -1068,7 +1068,7 @@ ed.add_branch = + Branch ed.goal.killall = Defeat all heroes ed.goal.shards = Take the shards ed.goal.heart = Destroy the gate -ed.goal.towers = Raze the knight towers +ed.goal.towers = Raze knight towers ed.goal.rivals = Defeat the rivals ed.goal.loot = Loot gold ed.goal.survive = Survive waves diff --git a/lang/it.lang b/lang/it.lang index 1a64d6a..0e85416 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -1064,7 +1064,7 @@ ed.add_branch = + Diramazione ed.goal.killall = Sconfiggi gli eroi ed.goal.shards = Prendi i frammenti ed.goal.heart = Distruggi la porta -ed.goal.towers = Radi al suolo le torri +ed.goal.towers = Abbatti le torri ed.goal.rivals = Sconfiggi i rivali ed.goal.loot = Saccheggia oro ed.goal.survive = Supera ondate @@ -1100,7 +1100,7 @@ ed.edit_blocks = Modifica ed.triggers = Trigger ed.triggers_hint = Un trigger esegue i suoi blocchi quando la sua condizione è soddisfatta. Le zone si disegnano nell'editor mappe. ed.add_trigger = + Trigger -ed.once = una volta +ed.once = 1 volta ed.enabled = attivo ed.edit_trigger_blocks = Blocchi ({0}) ed.pick_zone = - zona - diff --git a/lang/ru.lang b/lang/ru.lang index 922741a..6547552 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -1068,7 +1068,7 @@ ed.goal.towers = Снеси рыцарские башни ed.goal.rivals = Победи соперников ed.goal.loot = Награбь золота ed.goal.survive = Отбей волны -ed.goal.springs = Захвати источники маны +ed.goal.springs = Захвати источники ed.goal.freed = Освободи существ ed.map_random = Случайная карта ed.map_fixed = Своя карта diff --git a/src/editor/blocks_ui.rs b/src/editor/blocks_ui.rs index 52f94d0..ce630c1 100644 --- a/src/editor/blocks_ui.rs +++ b/src/editor/blocks_ui.rs @@ -36,6 +36,7 @@ pub struct BlockEdit { num_buf: String, // the number being typed (focused number field) num_focus: Option, help: String, + rows: std::collections::HashMap, // rows each block's fields needed (wrapping) } enum Op { @@ -424,7 +425,8 @@ impl BlockEdit { let cat = list[k].category(); let col = cat_color(cat); // the spawn block has many fields: two rows - let rows = if matches!(list[k], Block::Spawn { .. }) { 2.0 } else { 1.0 }; + // rows the fields needed last frame (they wrap when the block is too narrow) + let rows = self.rows.get(&fid(&path, 9999)).copied().unwrap_or(1) as f32; let header = Rect::new(x, y, w, CH() * ROW * rows); let problem = ctx.problems.iter().find(|(p, _)| *p == path).map(|(_, k)| *k); let selected = self.selected.as_ref() == Some(&path); @@ -447,8 +449,10 @@ impl BlockEdit { ops.push(Op::StartDrag(path.clone())); } let fy = y + (CH() * ROW - CH() * 1.5) * 0.5; - let mut cx = x + CW() * 1.8; - self.block_fields(im, gfx, &mut list[k], &path, &mut cx, fy, x + CW() * 3.0, ctx); + let cx = x + CW() * 1.8; + let mut lay = Lay { x: cx, y: fy, x2: x + CW() * 3.0, max: x + w - CW() * 2.5, rows: 1 }; + self.block_fields(im, gfx, &mut list[k], &path, &mut lay, ctx); + self.rows.insert(fid(&path, 9999), lay.rows); // a click on the block's free area selects it if im.clicked(header) { ops.push(Op::Select(path.clone())); @@ -501,37 +505,25 @@ impl BlockEdit { y - y0 } - // the fields inside a block's header row - #[allow(clippy::too_many_arguments)] - fn block_fields(&mut self, im: &mut Im, gfx: &Gfx, b: &mut Block, path: &[usize], cx: &mut f32, y: f32, x2: f32, ctx: &Ctx) { - let word = |cx: &mut f32, s: &str| { - gfx.text(s, *cx, y + CH() * 0.25, S(), TXT); - *cx += gfx.measure(s, S()) + CW() * 0.6; - }; + // the fields inside a block's header (they wrap into more rows when the block is too narrow) + fn block_fields(&mut self, im: &mut Im, gfx: &Gfx, b: &mut Block, path: &[usize], lay: &mut Lay, ctx: &Ctx) { let name = tr(&format!("blk.{}", b.kind_name())); - word(cx, &name); + lay.word(gfx, &name); let is_set = matches!(b, Block::SetVar { .. }); - let fh = CH() * 1.5; match b { Block::Message { text } | Block::Objective { text } | Block::Hint { text } => { - im.text_field(gfx, fid(path, 0), Rect::new(*cx, y, CW() * 44.0, fh), text, 200); - *cx += CW() * 45.0; + let w = (lay.max - lay.x).max(CW() * 24.0).min(CW() * 60.0); + let r = lay.take(w); + im.text_field(gfx, fid(path, 0), r, text, 200); } Block::Spawn { side, kind, count, level, place, name } => { - self.value(im, gfx, count, Ty::Num, path, 1, cx, y, ctx); - word(cx, "×"); - self.kind_dd(im, gfx, kind, false, path, 2, cx, y); - word(cx, &tr("blk.w_level")); - self.value(im, gfx, level, Ty::Num, path, 3, cx, y, ctx); - self.side_dd(im, gfx, side, false, path, 4, cx, y); - // second row: where, and the name - let y = y + CH() * ROW; - *cx = x2; - let word = |cx: &mut f32, s: &str| { - gfx.text(s, *cx, y + CH() * 0.25, S(), TXT); - *cx += gfx.measure(s, S()) + CW() * 0.6; - }; - word(cx, &tr("blk.w_at")); + self.value(im, gfx, count, Ty::Num, path, 1, lay, ctx); + lay.word(gfx, "×"); + self.kind_dd(im, gfx, kind, false, path, 2, lay); + lay.word(gfx, &tr("blk.w_level")); + self.value(im, gfx, level, Ty::Num, path, 3, lay, ctx); + self.side_dd(im, gfx, side, false, path, 4, lay); + lay.word(gfx, &tr("blk.w_at")); let mut opts = vec![tr("blk.place_core"), tr("blk.place_event")]; opts.extend(ctx.zones.iter().map(|z| trf("blk.place_zone", &[z.clone()]))); let cur = match place { @@ -539,124 +531,117 @@ impl BlockEdit { Place::Event => opts[1].clone(), Place::Zone(z) => trf("blk.place_zone", &[z.clone()]), }; - if let Some(c) = im.dropdown(gfx, fid(path, 5), Rect::new(*cx, y, CW() * 16.0, fh), &cur, &opts) { + let r = lay.take(CW() * 24.0); + if let Some(c) = im.dropdown(gfx, fid(path, 5), r, &cur, &opts) { *place = match c { 0 => Place::Core, 1 => Place::Event, c => Place::Zone(ctx.zones[c - 2].clone()), }; } - *cx += CW() * 17.0; - word(cx, &tr("blk.w_name")); - im.text_field(gfx, fid(path, 6), Rect::new(*cx, y, CW() * 10.0, fh), name, 16); - *cx += CW() * 11.0; + lay.word(gfx, &tr("blk.w_name")); + let r = lay.take(CW() * 10.0); + im.text_field(gfx, fid(path, 6), r, name, 16); } - Block::Wave { count } | Block::Gold { amount: count } | Block::Mana { amount: count } => self.value(im, gfx, count, Ty::Num, path, 1, cx, y, ctx), + Block::Wave { count } | Block::Gold { amount: count } | Block::Mana { amount: count } => self.value(im, gfx, count, Ty::Num, path, 1, lay, ctx), Block::SetVar { name, value } | Block::AddVar { name, value } => { - im.text_field(gfx, fid(path, 0), Rect::new(*cx, y, CW() * 12.0, fh), name, 24); - *cx += CW() * 13.0; - word(cx, &tr(if is_set { "blk.w_to" } else { "blk.w_by" })); - self.value(im, gfx, value, Ty::Num, path, 1, cx, y, ctx); + let r = lay.take(CW() * 12.0); + im.text_field(gfx, fid(path, 0), r, name, 24); + lay.word(gfx, &tr(if is_set { "blk.w_to" } else { "blk.w_by" })); + self.value(im, gfx, value, Ty::Num, path, 1, lay, ctx); } - Block::If { cond, .. } => self.value(im, gfx, cond, Ty::Bool, path, 1, cx, y, ctx), + Block::If { cond, .. } | Block::Until { cond, .. } | Block::DoUntil { cond, .. } => self.value(im, gfx, cond, Ty::Bool, path, 1, lay, ctx), Block::Repeat { times, .. } => { - self.value(im, gfx, times, Ty::Num, path, 1, cx, y, ctx); - word(cx, &tr("blk.w_times")); + self.value(im, gfx, times, Ty::Num, path, 1, lay, ctx); + lay.word(gfx, &tr("blk.w_times")); } - Block::Until { cond, .. } | Block::DoUntil { cond, .. } => self.value(im, gfx, cond, Ty::Bool, path, 1, cx, y, ctx), Block::Wait { secs } => { - self.value(im, gfx, secs, Ty::Num, path, 1, cx, y, ctx); - word(cx, &tr("blk.w_seconds")); + self.value(im, gfx, secs, Ty::Num, path, 1, lay, ctx); + lay.word(gfx, &tr("blk.w_seconds")); } Block::Win | Block::Lose => {} - Block::Reveal { zone } | Block::Camera { zone } => self.zone_dd(im, gfx, zone, path, 1, cx, y, ctx), + Block::Reveal { zone } | Block::Camera { zone } => self.zone_dd(im, gfx, zone, path, 1, lay, ctx), Block::Terrain { zone, kind } => { - self.zone_dd(im, gfx, zone, path, 1, cx, y, ctx); - word(cx, &tr("blk.w_to")); + self.zone_dd(im, gfx, zone, path, 1, lay, ctx); + lay.word(gfx, &tr("blk.w_to")); let opts: Vec = TERRAIN.iter().map(|t| tr(&format!("ed.terrain.{}", t))).collect(); let cur = tr(&format!("ed.terrain.{}", kind)); - if let Some(c) = im.dropdown(gfx, fid(path, 2), Rect::new(*cx, y, CW() * 14.0, fh), &cur, &opts) { + let r = lay.take(CW() * 19.0); + if let Some(c) = im.dropdown(gfx, fid(path, 2), r, &cur, &opts) { *kind = TERRAIN[c].to_string(); } - *cx += CW() * 15.0; } Block::Kill { zone, side } => { - self.side_dd(im, gfx, side, true, path, 2, cx, y); - word(cx, &tr("blk.w_in")); - self.zone_dd(im, gfx, zone, path, 1, cx, y, ctx); + self.side_dd(im, gfx, side, true, path, 2, lay); + lay.word(gfx, &tr("blk.w_in")); + self.zone_dd(im, gfx, zone, path, 1, lay, ctx); } Block::Trigger { name, on } => { let cur = if name.is_empty() { tr("ed.pick_trigger") } else { name.clone() }; - if let Some(c) = im.dropdown(gfx, fid(path, 1), Rect::new(*cx, y, CW() * 16.0, fh), &cur, &ctx.triggers) { + let r = lay.take(CW() * 16.0); + if let Some(c) = im.dropdown(gfx, fid(path, 1), r, &cur, &ctx.triggers) { *name = ctx.triggers[c].clone(); } - *cx += CW() * 17.0; let opts = vec![tr("blk.on"), tr("blk.off")]; - if let Some(c) = im.dropdown(gfx, fid(path, 2), Rect::new(*cx, y, CW() * 10.0, fh), &opts[if *on { 0 } else { 1 }], &opts) { + let r = lay.take(CW() * 10.0); + if let Some(c) = im.dropdown(gfx, fid(path, 2), r, &opts[if *on { 0 } else { 1 }], &opts) { *on = c == 0; } - *cx += CW() * 11.0; } } } #[allow(clippy::too_many_arguments)] - fn zone_dd(&mut self, im: &mut Im, gfx: &Gfx, zone: &mut String, path: &[usize], f: usize, cx: &mut f32, y: f32, ctx: &Ctx) { + fn zone_dd(&mut self, im: &mut Im, gfx: &Gfx, zone: &mut String, path: &[usize], f: usize, lay: &mut Lay, ctx: &Ctx) { let cur = if zone.is_empty() { tr("ed.pick_zone") } else { zone.clone() }; - if let Some(c) = im.dropdown(gfx, fid(path, f), Rect::new(*cx, y, CW() * 14.0, CH() * 1.5), &cur, &ctx.zones) { + let r = lay.take(CW() * 14.0); + if let Some(c) = im.dropdown(gfx, fid(path, f), r, &cur, &ctx.zones) { *zone = ctx.zones[c].clone(); } - *cx += CW() * 15.0; } #[allow(clippy::too_many_arguments)] - fn side_dd(&mut self, im: &mut Im, gfx: &Gfx, side: &mut i32, any: bool, path: &[usize], f: usize, cx: &mut f32, y: f32) { + fn side_dd(&mut self, im: &mut Im, gfx: &Gfx, side: &mut i32, any: bool, path: &[usize], f: usize, lay: &mut Lay) { let sides: Vec = SIDES.iter().copied().filter(|&s| any || s >= 0).collect(); let opts: Vec = sides.iter().map(|&s| side_label(s)).collect(); - if let Some(c) = im.dropdown(gfx, fid(path, f), Rect::new(*cx, y, CW() * 14.0, CH() * 1.5), &side_label(*side), &opts) { + let r = lay.take(CW() * 16.0); + if let Some(c) = im.dropdown(gfx, fid(path, f), r, &side_label(*side), &opts) { *side = sides[c]; } - *cx += CW() * 15.0; } #[allow(clippy::too_many_arguments)] - fn kind_dd(&mut self, im: &mut Im, gfx: &Gfx, kind: &mut String, any: bool, path: &[usize], f: usize, cx: &mut f32, y: f32) { + fn kind_dd(&mut self, im: &mut Im, gfx: &Gfx, kind: &mut String, any: bool, path: &[usize], f: usize, lay: &mut Lay) { let mut opts: Vec = Vec::new(); if any { opts.push(tr("ed.any_kind")); } opts.extend(KINDS.iter().map(|k| kind_label(k))); - if let Some(c) = im.dropdown(gfx, fid(path, f), Rect::new(*cx, y, CW() * 14.0, CH() * 1.5), &kind_label(kind), &opts) { + let r = lay.take(CW() * 17.0); + if let Some(c) = im.dropdown(gfx, fid(path, f), r, &kind_label(kind), &opts) { *kind = if any && c == 0 { String::new() } else { KINDS[c - any as usize].to_string() }; } - *cx += CW() * 15.0; } // a value slot: a small switch for the value's kind, then the value itself #[allow(clippy::too_many_arguments)] - fn value(&mut self, im: &mut Im, gfx: &Gfx, v: &mut Val, want: Ty, path: &[usize], f: usize, cx: &mut f32, y: f32, ctx: &Ctx) { - let fh = CH() * 1.5; + fn value(&mut self, im: &mut Im, gfx: &Gfx, v: &mut Val, want: Ty, path: &[usize], f: usize, lay: &mut Lay, ctx: &Ctx) { let kinds: &[&str] = if want == Ty::Num { &NUM_VALUES } else { &BOOL_VALUES }; let opts: Vec = kinds.iter().map(|k| tr(&format!("blk.v.{}", k))).collect(); // the kind switch - let sw_ = Rect::new(*cx, y, CW() * 1.6, fh); + let sw_ = lay.take_tight(CW() * 1.6); draw_rectangle(sw_.x, sw_.y, sw_.w, sw_.h, Color::new(0.0, 0.0, 0.0, 0.35)); if let Some(c) = im.dropdown(gfx, fid(path, f * 31 + 1), sw_, "", &opts) { if let Some(nv) = Val::template(kinds[c]) { *v = nv; } } - *cx += CW() * 1.8; - let word = |cx: &mut f32, s: &str| { - gfx.text(s, *cx, y + CH() * 0.25, S(), TXT); - *cx += gfx.measure(s, S()) + CW() * 0.5; - }; let sub = |k: usize| f * 31 + 2 + k; let and = matches!(v, Val::And { .. }); match v { Val::Num { n } => { let id_ = fid(path, f * 31); - let r = Rect::new(*cx, y, CW() * 8.0, fh); + let r = lay.take(CW() * 8.0); let mut s = if im.focus == Some(id_) && self.num_focus == Some(id_) { self.num_buf.clone() } else { fmt_num(*n) }; if im.focus == Some(id_) && self.num_focus != Some(id_) { self.num_focus = Some(id_); @@ -668,71 +653,112 @@ impl BlockEdit { *n = x; } } - *cx += CW() * 8.6; } Val::Bool { b } => { let o = vec![tr("blk.yes"), tr("blk.no")]; - if let Some(c) = im.dropdown(gfx, fid(path, sub(0)), Rect::new(*cx, y, CW() * 7.0, fh), &o[if *b { 0 } else { 1 }], &o) { + let r = lay.take(CW() * 7.0); + if let Some(c) = im.dropdown(gfx, fid(path, sub(0)), r, &o[if *b { 0 } else { 1 }], &o) { *b = c == 0; } - *cx += CW() * 7.6; } Val::Var { name } => { let cur = if name.is_empty() { tr("ed.pick_var") } else { name.clone() }; - if let Some(c) = im.dropdown(gfx, fid(path, sub(0)), Rect::new(*cx, y, CW() * 12.0, fh), &cur, &ctx.vars) { + let r = lay.take(CW() * 15.0); + if let Some(c) = im.dropdown(gfx, fid(path, sub(0)), r, &cur, &ctx.vars) { *name = ctx.vars[c].clone(); } - *cx += CW() * 12.6; } - Val::Gold | Val::Mana | Val::Time => word(cx, &tr(&format!("blk.v.{}", v.kind_name()))), + Val::Gold | Val::Mana | Val::Time => lay.word(gfx, &tr(&format!("blk.v.{}", v.kind_name()))), Val::Creatures { side, kind } => { - word(cx, &tr("blk.v.creatures")); - self.kind_dd(im, gfx, kind, true, path, sub(0), cx, y); - self.side_dd(im, gfx, side, true, path, sub(1), cx, y); + lay.word(gfx, &tr("blk.v.creatures")); + self.kind_dd(im, gfx, kind, true, path, sub(0), lay); + self.side_dd(im, gfx, side, true, path, sub(1), lay); } Val::InZone { zone, side, kind } => { - word(cx, &tr("blk.v.inzone")); - self.kind_dd(im, gfx, kind, true, path, sub(0), cx, y); - self.side_dd(im, gfx, side, true, path, sub(1), cx, y); - word(cx, &tr("blk.w_in")); - self.zone_dd(im, gfx, zone, path, sub(2), cx, y, ctx); + lay.word(gfx, &tr("blk.v.inzone")); + self.kind_dd(im, gfx, kind, true, path, sub(0), lay); + self.side_dd(im, gfx, side, true, path, sub(1), lay); + lay.word(gfx, &tr("blk.w_in")); + self.zone_dd(im, gfx, zone, path, sub(2), lay, ctx); } Val::Random { a, b } => { - word(cx, &tr("blk.v.random")); - self.value(im, gfx, a, Ty::Num, path, sub(0), cx, y, ctx); - word(cx, ".."); - self.value(im, gfx, b, Ty::Num, path, sub(1), cx, y, ctx); + lay.word(gfx, &tr("blk.v.random")); + self.value(im, gfx, a, Ty::Num, path, sub(0), lay, ctx); + lay.word(gfx, ".."); + self.value(im, gfx, b, Ty::Num, path, sub(1), lay, ctx); } Val::Math { op, a, b } | Val::Cmp { op, a, b } => { - let ops: Vec = if matches!(v_kind_is_math(op), true) { MATH_OPS.iter().map(|s| s.to_string()).collect() } else { CMP_OPS.iter().map(|s| s.to_string()).collect() }; - word(cx, "("); - self.value(im, gfx, a, Ty::Num, path, sub(0), cx, y, ctx); - if let Some(c) = im.dropdown(gfx, fid(path, sub(1)), Rect::new(*cx, y, CW() * 4.0, fh), op, &ops) { + let ops: Vec = if v_kind_is_math(op) { MATH_OPS.iter().map(|s| s.to_string()).collect() } else { CMP_OPS.iter().map(|s| s.to_string()).collect() }; + lay.word(gfx, "("); + self.value(im, gfx, a, Ty::Num, path, sub(0), lay, ctx); + let r = lay.take(CW() * 4.0); + if let Some(c) = im.dropdown(gfx, fid(path, sub(1)), r, op, &ops) { *op = ops[c].clone(); } - *cx += CW() * 4.6; - self.value(im, gfx, b, Ty::Num, path, sub(2), cx, y, ctx); - word(cx, ")"); + self.value(im, gfx, b, Ty::Num, path, sub(2), lay, ctx); + lay.word(gfx, ")"); } Val::And { a, b } | Val::Or { a, b } => { - word(cx, "("); - self.value(im, gfx, a, Ty::Bool, path, sub(0), cx, y, ctx); - word(cx, &tr(if and { "blk.and" } else { "blk.or" })); - self.value(im, gfx, b, Ty::Bool, path, sub(1), cx, y, ctx); - word(cx, ")"); + lay.word(gfx, "("); + self.value(im, gfx, a, Ty::Bool, path, sub(0), lay, ctx); + lay.word(gfx, &tr(if and { "blk.and" } else { "blk.or" })); + self.value(im, gfx, b, Ty::Bool, path, sub(1), lay, ctx); + lay.word(gfx, ")"); } Val::Not { a } => { - word(cx, &tr("blk.not")); - self.value(im, gfx, a, Ty::Bool, path, sub(0), cx, y, ctx); + lay.word(gfx, &tr("blk.not")); + self.value(im, gfx, a, Ty::Bool, path, sub(0), lay, ctx); } Val::EventKind { kind } => { - word(cx, &tr("blk.v.eventkind")); - self.kind_dd(im, gfx, kind, false, path, sub(0), cx, y); + lay.word(gfx, &tr("blk.v.eventkind")); + self.kind_dd(im, gfx, kind, false, path, sub(0), lay); } } } } +// where the next field of a block goes: left to right, into a new row at the block's edge +struct Lay { + x: f32, + y: f32, + x2: f32, // start of a new row + max: f32, // right edge + rows: usize, +} + +impl Lay { + fn need(&mut self, w: f32) { + if self.x + w > self.max && self.x > self.x2 + 1.0 { + self.x = self.x2; + self.y += CH() * ROW; + self.rows += 1; + } + } + + // room for a field of width w (wraps if needed) + fn take(&mut self, w: f32) -> Rect { + self.need(w); + let r = Rect::new(self.x, self.y, w, CH() * 1.5); + self.x += w + CW() * 0.6; + r + } + + fn take_tight(&mut self, w: f32) -> Rect { + // the kind switch stays with its value: room for both + self.need(w + CW() * 8.0); + let r = Rect::new(self.x, self.y, w, CH() * 1.5); + self.x += w + CW() * 0.2; + r + } + + fn word(&mut self, gfx: &Gfx, s: &str) { + let w = gfx.measure(s, S()); + self.need(w); + gfx.text(s, self.x, self.y + CH() * 0.25, S(), TXT); + self.x += w + CW() * 0.6; + } +} + // math operators are + - * /, comparisons the rest fn v_kind_is_math(op: &str) -> bool { MATH_OPS.contains(&op) diff --git a/src/editor/map.rs b/src/editor/map.rs index 57a8683..0129b27 100644 --- a/src/editor/map.rs +++ b/src/editor/map.rs @@ -779,10 +779,10 @@ impl MapEdit { bx += gfx.measure(&tr("ed.fill"), S()) + CW() * 4.0; } let mirrors: Vec = (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() * 18.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; } - bx += CW() * 19.0; + bx += CW() * 24.0; let uw = gfx.measure(&tr("ed.undo"), S()) + CW() * 2.0; if im.button(gfx, Rect::new(bx, by, uw, CH() * 1.7), &tr("ed.undo")) { self.undo(); diff --git a/src/editor/templates.rs b/src/editor/templates.rs index e7e842d..fcb183e 100644 --- a/src/editor/templates.rs +++ b/src/editor/templates.rs @@ -107,7 +107,9 @@ pub fn demo_campaign() -> CampaignDoc { let (ax, ay) = cl(cx + 7, cy - 2); let (bx, by) = cl(cx + 10, cy + 2); let vault = zone_rect("vault", ax, ay, bx, by); - let (dx, dy) = cl(cx - 2, cy + 6); + // the den lies behind a band of earth west of the dungeon (away from the knights' way to the + // heart, who dig through earth): dig the tunnel first + let (dx, dy) = cl((cx - 11).max(5), cy + 1); let den = ZoneDoc { name: "den".into(), shape: "circle".into(), at: Some((dx as f32, dy as f32)), radius: Some(2.5), tiles: None, from: None, to: None, width: None }; let (lx0, ly0) = cl(cx + 14, cy - 8); let (lx1, ly1) = cl(cx + 14, cy + 8); @@ -115,13 +117,29 @@ pub fn demo_campaign() -> CampaignDoc { let (gx0, gy0) = cl(cx + 16, cy - 5); let (gx1, gy1) = cl(cx + 22, cy + 5); let guard = zone_rect("field", gx0, gy0, gx1, gy1); - let dig_tiles: Vec<(i32, i32)> = (0..3).flat_map(|k| [(cx - 6 - k, cy), (cx - 6 - k, cy + 1)]).map(|(x, y)| cl(x, y)).collect(); + let dig_tiles: Vec<(i32, i32)> = (0..3).flat_map(|k| [(cx - 5 - k, cy + 1), (cx - 5 - k, cy + 2)]).map(|(x, y)| cl(x, y)).collect(); let dig = ZoneDoc { name: "tunnel".into(), shape: "tiles".into(), at: None, radius: None, tiles: Some(dig_tiles.clone()), from: None, to: None, width: None }; - // the den is open ground, the tunnel solid earth to dig - for i in crate::mscript::zone_tiles(&den) { - if g.map.t[i].solid() { + // the den is open ground, sealed by earth all around; the tunnel is solid earth to dig + let den_tiles = crate::mscript::zone_tiles(&den); + for &i in &den_tiles { + let (x, y) = crate::map::xy(i); + for (ox, oy) in [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] { + if !crate::map::inb(x + ox, y + oy) { + continue; + } + let j = crate::map::idx(x + ox, y + oy); + if !den_tiles.contains(&j) && !g.map.t[j].solid() { + let t = &mut g.map.t[j]; + t.kind = crate::map::Kind::Earth; + t.owner = crate::map::Owner::None; + } + } + } + for &i in &den_tiles { + if g.map.t[i].solid() || g.map.t[i].liquid() { g.dig_out(i); } + g.map.t[i].owner = crate::map::Owner::None; } for &(x, y) in &dig_tiles { let t = &mut g.map.t[crate::map::idx(x, y)]; @@ -233,12 +251,14 @@ mod tests { // it starts on its painted map and the scripts run without errors let mut g = crate::campaign::start_editor_mission(&doc.missions[0], 0, 99); assert!(g.script.is_some()); + for _ in 0..(20 * 40) { g.update(0.05); } let s = g.script.as_ref().unwrap(); assert!(s.error.is_none(), "{:?}", s.error); assert!(s.vars.contains_key("kills"), "the start script ran"); + assert!(!s.log.iter().any(|(_, n)| n == "den_enter" || n == "tunnel_dug"), "the den waits behind its tunnel: {:?}", s.log); // the random-map mission starts too let g2 = crate::campaign::start_editor_mission(&doc.missions[1], 1, 99); assert!(g2.mission.is_some());