Campaign mission system: 20 mission definitions, goals, unlocks, carry-over
- campaign.rs: missions as key data (size, rivers, fortresses, goals, waves, time limit, gifts); start_mission builds the map with the generator; tools unlock step by step; the strongest 5 creatures and 20% of the gold are carried into the next mission; campaign progress type - Map generator options (portals, treasure caves, hero gate) and meandering rivers with knight bridges - Mission goals: shards, gate of the order, all knights, vault loot, knight towers, waves; time limits; waves and gate defenders only where the mission wants them - Locked tools are shown dark and cannot be chosen; HUD shows gate/waves/time only when used - Fix: patrol routes no longer start on tower spots - Tests: every mission builds, its goals are possible, wave heroes reach the core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e15b16cc60
commit
46755d68ac
8 files changed
+758
-85
No files matched your search
@@ -254,6 +254,7 @@ enum TBtn {
|
||||
|
||||
pub enum UiAction {
|
||||
None,
|
||||
Locked, // a tool that is not available yet (campaign)
|
||||
OpenMenu,
|
||||
SelectTool(Tool),
|
||||
MinimapJump(f32, f32),
|
||||
@@ -371,6 +372,7 @@ impl Ui {
|
||||
self.open_cat = None;
|
||||
UiAction::OpenMenu
|
||||
}
|
||||
TBtn::Tool(i) if !g.tool_allowed(i) => UiAction::Locked,
|
||||
TBtn::Tool(i) => {
|
||||
self.open_cat = None;
|
||||
self.tip = Some((i, get_time()));
|
||||
@@ -378,7 +380,9 @@ impl Ui {
|
||||
}
|
||||
TBtn::Cat(c) => {
|
||||
let tools = CATS[c].1;
|
||||
if tools.len() == 1 {
|
||||
if tools.len() == 1 && !g.tool_allowed(tools[0]) {
|
||||
UiAction::Locked
|
||||
} else if tools.len() == 1 {
|
||||
self.open_cat = None;
|
||||
self.tip = Some((tools[0], get_time()));
|
||||
UiAction::SelectTool(TOOLS[tools[0]])
|
||||
@@ -430,13 +434,22 @@ impl Ui {
|
||||
gold_seg,
|
||||
vec![txt(tr("hud.mana"), MANA), Part::Gap, Part::Bar(g.mana / MANA_MAX, MANA), Part::Gap, txt(format!("{:<3}", g.mana as i32), TXT)],
|
||||
vec![txt(tr("hud.core"), RED), Part::Gap, Part::Bar(g.core_hp / CORE_HP, RED)],
|
||||
vec![txt(tr("hud.gate"), BLUE), Part::Gap, Part::Bar(g.gate_hp / GATE_HP, BLUE)],
|
||||
vec![txt(tr("hud.creatures"), TXT), Part::Gap, txt(format!("{}/{}", g.creature_count(), cap), TXT)],
|
||||
vec![txt(tr("hud.workers"), TXT), Part::Gap, txt(g.worker_count().to_string(), TXT)],
|
||||
vec![txt(trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col), Part::Gap, txt(mmss(g.wave_timer), TXT)],
|
||||
vec![txt(tr("hud.payday"), if g.payday <= PAYDAY_WARN { RED } else { GOLD }), Part::Gap, txt(mmss(g.payday), TXT)],
|
||||
vec![txt(tr("hud.army"), TXT), Part::Gap, txt((g.army_value() as i32).to_string(), TXT)],
|
||||
];
|
||||
// campaign missions: gate, waves and time limit only where the mission has them
|
||||
if g.has_gate() {
|
||||
segs.insert(3, vec![txt(tr("hud.gate"), BLUE), Part::Gap, Part::Bar(g.gate_hp / GATE_HP, BLUE)]);
|
||||
}
|
||||
if g.waves_on() {
|
||||
segs.push(vec![txt(trf("hud.wave", &[(g.wave + 1).to_string()]), wave_col), Part::Gap, txt(mmss(g.wave_timer), TXT)]);
|
||||
}
|
||||
if let Some(limit) = g.mission.as_ref().map(|m| m.time_limit).filter(|&l| l > 0.0) {
|
||||
let left = (limit - g.time).max(0.0);
|
||||
segs.insert(0, vec![txt(tr("hud.time_left"), if left < 120.0 { RED } else { GOLD }), Part::Gap, txt(mmss(left), TXT)]);
|
||||
}
|
||||
segs.push(vec![txt(tr("hud.payday"), if g.payday <= PAYDAY_WARN { RED } else { GOLD }), Part::Gap, txt(mmss(g.payday), TXT)]);
|
||||
segs.push(vec![txt(tr("hud.army"), TXT), Part::Gap, txt((g.army_value() as i32).to_string(), TXT)]);
|
||||
if !g.hand.is_empty() {
|
||||
segs.push(vec![txt(tr("hud.hand"), FRAME_HI), Part::Gap, txt(format!("{}/8", g.hand.len()), TXT)]);
|
||||
}
|
||||
@@ -499,7 +512,8 @@ impl Ui {
|
||||
let px = K(); // one pixel of the font, times the factor
|
||||
let pulse = (now * 2.5) as i64 % 2 == 0;
|
||||
// one toolbar button: icon, optional key, label in small text
|
||||
let tool_button = |bx: f32, by: f32, bw: f32, icon_id: u32, key: &str, label: &str, sel: bool, hovered: bool| {
|
||||
// locked tools (campaign: not yet available) are drawn dark
|
||||
let tool_button = |bx: f32, by: f32, bw: f32, icon_id: u32, key: &str, label: &str, sel: bool, hovered: bool, locked: bool| {
|
||||
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
|
||||
draw_rectangle(r.x, r.y, r.w, r.h, if sel { Color::new(0.42, 0.26, 0.1, 1.0) } else if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
|
||||
// the active tool: pulsing frame
|
||||
@@ -507,7 +521,7 @@ impl Ui {
|
||||
draw_rectangle_lines(r.x, r.y, r.w, r.h, if sel { 3.0 * px } else { 2.0 * px }, frame);
|
||||
// icon: the 16 px atlas cell at 2*F, centred on the grid
|
||||
let icon = 32.0 * px;
|
||||
gfx.draw_cell(icon_id, bx + (bw - icon) * 0.5, by + 4.0 * px, icon, icon, WHITE);
|
||||
gfx.draw_cell(icon_id, bx + (bw - icon) * 0.5, by + 4.0 * px, icon, icon, if locked { Color::new(0.25, 0.22, 0.2, 1.0) } else { WHITE });
|
||||
if !key.is_empty() {
|
||||
gfx.text(key, bx + 3.0 * px, by + 3.0 * px, SMALL(), if sel { FRAME_HI } else { DIM });
|
||||
}
|
||||
@@ -516,7 +530,7 @@ impl Ui {
|
||||
let label: String = label.chars().take(maxc.min(11)).collect();
|
||||
let lw = gfx.measure(&label, SMALL());
|
||||
let lx = bx + ((bw - lw) / (GW * SMALL()) / 2.0).floor() * GW * SMALL();
|
||||
gfx.text(&label, lx, by + 2.0 * CH() + 4.0 * px, SMALL(), if sel { FRAME_HI } else { TXT });
|
||||
gfx.text(&label, lx, by + 2.0 * CH() + 4.0 * px, SMALL(), if sel { FRAME_HI } else if locked { DIM } else { TXT });
|
||||
r
|
||||
};
|
||||
let mut popup_top = tb_y;
|
||||
@@ -540,7 +554,8 @@ impl Ui {
|
||||
if hovered && tools.len() == 1 {
|
||||
hover_tool = Some(tools[0]);
|
||||
}
|
||||
let r = tool_button(bx, by, bw, ti.icon, key, &label, has, self.open_cat == Some(c) || hovered);
|
||||
let locked = !tools.iter().any(|&t| g.tool_allowed(t));
|
||||
let r = tool_button(bx, by, bw, ti.icon, key, &label, has, self.open_cat == Some(c) || hovered, locked);
|
||||
if has {
|
||||
gfx.glyph(0x1F, bx + (bw - CW()) * 0.5, tb_y, S(), if pulse { WHITE } else { FRAME_HI });
|
||||
}
|
||||
@@ -701,7 +716,7 @@ impl Ui {
|
||||
hover_tool = Some(i);
|
||||
}
|
||||
let key = if touch_ui() { "" } else { keys[i].as_str() };
|
||||
let r = tool_button(bx, by, bw, ti.icon, key, &ti.label, self.tool == TOOLS[i], hovered);
|
||||
let r = tool_button(bx, by, bw, ti.icon, key, &ti.label, self.tool == TOOLS[i], hovered, !g.tool_allowed(i));
|
||||
self.touch_btns.push((r, TBtn::Tool(i)));
|
||||
}
|
||||
popup_top = py0;
|
||||
|
||||
Reference in new issue
Block a user