Editor: blocks wrap their fields, wider fields, the demo den behind a tunnel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-05 19:48:44 +01:00
1 parent caad441c72
commit a0df40ab19
7 files changed
+166 -120

No files matched your search

+1 -1
View File
@@ -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
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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 -
+1 -1
View File
@@ -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 = Своя карта
+134 -108
View File
@@ -36,6 +36,7 @@ pub struct BlockEdit {
num_buf: String, // the number being typed (focused number field)
num_focus: Option<u64>,
help: String,
rows: std::collections::HashMap<u64, usize>, // 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<String> = 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<i32> = SIDES.iter().copied().filter(|&s| any || s >= 0).collect();
let opts: Vec<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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)
+2 -2
View File
@@ -779,10 +779,10 @@ impl MapEdit {
bx += gfx.measure(&tr("ed.fill"), 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() * 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();
+25 -5
View File
@@ -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());