Master talents: experience, points and four branches (Builder, Warlord, Warlock, Merchant) with a toolbar window

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-06 11:35:11 +01:00
1 parent b93bcd89c4
commit bd956a4f57
28 files changed
+720 -22

No files matched your search

+106 -2
View File
@@ -457,6 +457,8 @@ enum TBtn {
Tool(usize),
Menu,
Overview,
Talents, // the master's talent window
Learn(u8), // spend a point on a branch
OvRow(u32),
OvScroll(i32),
GoldTo, // the next ally to receive gold
@@ -478,6 +480,7 @@ pub enum UiAction {
ChatSetTeam(bool),
ChatSend,
SendGold(u8, u32), // gold for an ally of a match (side, amount)
LearnTalent(u8), // spend a talent point on a branch
Consumed,
}
@@ -485,6 +488,7 @@ pub struct Ui {
pub tool: Tool,
pub show_help: bool,
pub show_overview: bool, // list of all creatures
pub show_talents: bool, // the master's talents
pub coop_end: Option<(bool, bool, bool)>, // end of an editor co-op mission: (host, a next mission, asked for it)
gold_to: usize, // the ally chosen in the creature overview
ov_scroll: usize,
@@ -547,6 +551,28 @@ pub fn wrap(s: &str, cols: usize) -> Vec<String> {
out
}
// what a talent branch gives at its level now, and at the next level
fn branch_text(t: &crate::talents::Talents, b: usize) -> String {
use crate::talents::*;
let desc = |lv: u8| {
let l = lv as i32;
let (a, c) = match b {
BUILDER => (10 * l, 6 * l),
WARLORD => (5 * l, 5 * l),
WARLOCK => (15 * l, 6 * l),
_ => (6 * l, 6 * l),
};
trf(&format!("{}.desc", NAMES[b]), &[a.to_string(), c.to_string()])
};
let lv = t.lv[b];
let now = if lv == 0 { tr("talent.none") } else { desc(lv) };
if lv < MAX_LEVEL {
format!("{} {}", now, trf("talent.then", &[desc(lv + 1)]))
} else {
now
}
}
#[cfg(test)]
mod wrap_tests {
#[test]
@@ -573,6 +599,7 @@ impl Ui {
tool: Tool::Dig,
show_help: true,
show_overview: false,
show_talents: false,
coop_end: None,
gold_to: 0,
ov_scroll: 0,
@@ -664,9 +691,17 @@ impl Ui {
TBtn::Overview => {
self.open_cat = None;
self.show_overview = !self.show_overview;
self.show_talents = false;
self.ov_scroll = 0;
UiAction::Consumed
}
TBtn::Talents => {
self.open_cat = None;
self.show_talents = !self.show_talents;
self.show_overview = false;
UiAction::Consumed
}
TBtn::Learn(b) => UiAction::LearnTalent(b),
TBtn::OvScroll(d) => {
self.ov_scroll = (self.ov_scroll as i32 + d).max(0) as usize;
UiAction::Consumed
@@ -891,7 +926,7 @@ impl Ui {
let mut popup_top = tb_y;
// categories + menu button; a category with several tools opens a row above
// (the same layout for mouse and touch; on the desktop the keys still select tools directly)
let n = CATS.len() + 2;
let n = CATS.len() + 3;
let bcols = (((cols - 2) as usize / n) as f32).clamp(4.0, 8.0);
let bw = bcols * CW();
let x0 = grid_center(sw, bw * n as f32);
@@ -932,10 +967,27 @@ impl Ui {
let ol: String = ol.chars().take(maxc).collect();
gfx.text(&ol, bx + (bw - gfx.measure(&ol, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
self.touch_btns.push((r, TBtn::Overview));
// menu button
// the master's talents: the frame pulses while points are free
let bx = x0 + (CATS.len() + 1) as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my));
let free = g.talents.free();
let glow = free > 0 && ((now * 3.0) as i64) % 2 == 0;
draw_rectangle(r.x, r.y, r.w, r.h, if hovered || self.show_talents { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, if glow { GOLD } else if self.show_talents { FRAME_HI } else { Color::new(0.35, 0.28, 0.2, 1.0) });
let isz = 2.0 * CH() - 2.0 * px;
draw_texture_ex(&gfx.atlas, bx + (bw - isz) * 0.5, by + 2.0 * px, WHITE, DrawTextureParams { source: Some(Gfx::cell(I_RUNE)), dest_size: Some(vec2(isz, isz)), ..Default::default() });
if free > 0 {
gfx.text(&free.to_string(), r.x + 3.0 * px, r.y + 3.0 * px, SMALL(), if glow { WHITE } else { GOLD });
}
let tl = tr("cat.master");
let tl: String = tl.chars().take(maxc).collect();
gfx.text(&tl, bx + (bw - gfx.measure(&tl, SMALL())) * 0.5, by + 2.0 * CH() + 4.0 * px, SMALL(), TXT);
self.touch_btns.push((r, TBtn::Talents));
// menu button
let bx = x0 + (CATS.len() + 2) as f32 * bw;
let r = Rect::new(bx + px, by, bw - 2.0 * px, 3.0 * CH());
let hovered = !touch_ui() && r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hovered { Color::new(0.18, 0.13, 0.1, 1.0) } else { Color::new(0.1, 0.07, 0.07, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0 * px, Color::new(0.35, 0.28, 0.2, 1.0));
gfx.glyph('≡', bx + (bw - GW * TITLE()) * 0.5, by + 2.0 * px, TITLE(), FRAME_HI);
@@ -1204,6 +1256,9 @@ impl Ui {
if self.show_overview {
self.draw_overview(g, gfx, tb_y);
}
if self.show_talents {
self.draw_talents(g, gfx, tb_y);
}
// ---------------- Tooltip
if let Some(i) = hover_tool {
@@ -1232,6 +1287,55 @@ impl Ui {
}
// all creatures, grouped by kind: level, mood, wage, job, health; a click jumps there
// the master's talents: four branches of five levels, points from experience
fn draw_talents(&mut self, g: &Game, gfx: &Gfx, bottom: f32) {
use crate::talents::*;
let (mx, my) = mouse();
let t = &g.talents;
let cols = ((sw() / CW()) as i32 - 2).min(48);
let inner = (cols - 4) as usize;
// the lines of each branch: name and level, what it gives now (wrapped)
let rows_of = |b: usize| wrap(&branch_text(t, b), inner.saturating_sub(6)).len() as i32 + 2;
let rows = 5 + (0..BRANCHES).map(rows_of).sum::<i32>();
let x = grid_center(sw(), cols as f32 * CW()).max(0.0);
let y = (bottom - rows as f32 * CH() - CH()).max(CH() * 4.0);
gfx.window_titled(x, y, cols, rows, S(), false, FRAME_HI, BG, &tr("talent.title"), FRAME_HI);
self.rects.push(Rect::new(x, y, cols as f32 * CW(), rows as f32 * CH()));
let ix = x + CW() * 2.0;
let mut yy = y + CH();
// points and the way to the next one
gfx.text(&trf("talent.points", &[t.free().to_string()]), ix, yy, S(), if t.free() > 0 { GOLD } else { TXT });
yy += CH() * 1.2;
match t.progress() {
Some((have, need)) => {
ascii_bar(gfx, ix, yy, 16, (have / need).clamp(0.0, 1.0), MANA, SMALL());
gfx.text(&trf("talent.next", &[(have as i32).to_string(), (need as i32).to_string()]), ix + CW() * 17.0, yy, SMALL(), DIM);
}
None => gfx.text(&tr("talent.all"), ix, yy, SMALL(), DIM),
}
yy += CH() * 1.8;
for b in 0..BRANCHES {
let lv = t.lv[b];
let pips: String = (0..MAX_LEVEL).map(|k| if k < lv { '■' } else { '·' }).collect();
gfx.text(&format!("{} {}", tr(NAMES[b]), pips), ix, yy, S(), FRAME_HI);
// a button to learn the next level
if t.can_learn(b) {
let r = Rect::new(x + (cols - 7) as f32 * CW(), yy - CH() * 0.2, CW() * 4.0, CH() * 1.6);
let hov = r.contains(vec2(mx, my));
draw_rectangle(r.x, r.y, r.w, r.h, if hov { Color::new(0.35, 0.22, 0.1, 1.0) } else { Color::new(0.15, 0.1, 0.08, 1.0) });
draw_rectangle_lines(r.x, r.y, r.w, r.h, 2.0, GOLD);
gfx.text("+", r.x + (r.w - gfx.measure("+", S())) * 0.5, r.y + (r.h - GH * S()) * 0.5, S(), GOLD);
self.touch_btns.push((r, TBtn::Learn(b as u8)));
}
yy += CH() * 1.2;
for l in wrap(&branch_text(t, b), inner.saturating_sub(6)) {
gfx.text(&l, ix + CW() * 2.0, yy, SMALL(), DIM);
yy += CH();
}
yy += CH() * 0.8;
}
}
fn draw_overview(&mut self, g: &Game, gfx: &Gfx, bottom: f32) {
let (sw, _) = (sw(), sh());
let (mx, my) = mouse();