From 9b49bc2c9dda3d8d58eaf7c8709b66375b588a23 Mon Sep 17 00:00:00 2001 From: Richard Zink Date: Thu, 5 Nov 2026 23:44:03 +0100 Subject: [PATCH] Room levels 2 and 3, researched in the library: a quarter better each, brighter, with gold trim Co-Authored-By: Claude Opus 5.5 --- lang/cs.lang | 1 + lang/da.lang | 1 + lang/de.lang | 1 + lang/el.lang | 1 + lang/en.lang | 1 + lang/es.lang | 1 + lang/fi.lang | 1 + lang/hr.lang | 1 + lang/hu.lang | 1 + lang/it.lang | 1 + lang/nl.lang | 1 + lang/no.lang | 1 + lang/pt.lang | 1 + lang/ro.lang | 1 + lang/ru.lang | 1 + lang/sk.lang | 1 + lang/sr.lang | 1 + lang/sv.lang | 1 + src/game.rs | 43 +++++++++++++++++++++++------ src/map.rs | 4 ++- src/render3d.rs | 38 +++++++++++++++++++++++++ src/research.rs | 73 ++++++++++++++++++++++++++++++++++++++++++++++++- src/ui.rs | 5 ++++ 23 files changed, 171 insertions(+), 10 deletions(-) diff --git a/lang/cs.lang b/lang/cs.lang index d4099f1..0fa3752 100644 --- a/lang/cs.lang +++ b/lang/cs.lang @@ -939,6 +939,7 @@ research.now = Zkoumá se: {0} ({1} %) research.next = pak: {0} research.done = Tady už nezbývá nic ke zkoumání. msg.researched = Vyzkoumáno: {0}! Je to teď v nabídce stavby. +msg.room_upgraded = Výzkum: {0} dosahuje úrovně {1} – funguje o čtvrtinu lépe a je to vidět. msg.tool_research = Ještě nevyzkoumáno - pracují na tom tvorové studující v knihovně. msg.script = {0} diff --git a/lang/da.lang b/lang/da.lang index dc102e4..6dbafd8 100644 --- a/lang/da.lang +++ b/lang/da.lang @@ -938,6 +938,7 @@ research.now = Forsker i: {0} ({1}%) research.next = derefter: {0} research.done = Intet mere at forske i her. msg.researched = Udforsket: {0}! Det findes nu i byggemenuen. +msg.room_upgraded = Forskning: {0} når niveau {1} – virker en fjerdedel bedre, og det kan ses. msg.tool_research = Endnu ikke udforsket - væsner, der studerer i biblioteket, arbejder på det. msg.script = {0} diff --git a/lang/de.lang b/lang/de.lang index 3d2af8f..ce1231b 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -938,6 +938,7 @@ research.now = Erforscht: {0} ({1}%) research.next = danach: {0} research.done = Hier gibt es nichts mehr zu erforschen. msg.researched = Erforscht: {0}! Jetzt im Baumenü. +msg.room_upgraded = Forschung: {0} erreicht Stufe {1} – arbeitet ein Viertel besser, und man sieht es. msg.tool_research = Noch nicht erforscht - Kreaturen in der Bibliothek arbeiten daran. msg.script = {0} diff --git a/lang/el.lang b/lang/el.lang index 8abb891..71e8bd8 100644 --- a/lang/el.lang +++ b/lang/el.lang @@ -939,6 +939,7 @@ research.now = Έρευνα: {0} ({1}%) research.next = μετά: {0} research.done = Δεν μένει τίποτα για έρευνα εδώ. msg.researched = Ερευνήθηκε: {0}! Είναι πλέον στο μενού κατασκευής. +msg.room_upgraded = Έρευνα: {0} φτάνει στο επίπεδο {1} – δουλεύει ένα τέταρτο καλύτερα, και φαίνεται. msg.tool_research = Δεν έχει ερευνηθεί ακόμη - τα πλάσματα που μελετούν στη βιβλιοθήκη δουλεύουν πάνω του. msg.script = {0} diff --git a/lang/en.lang b/lang/en.lang index fd62083..ca42a67 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -939,6 +939,7 @@ research.now = Researching: {0} ({1}%) research.next = then: {0} research.done = Nothing left to research here. msg.researched = Researched: {0}! It is in the build menu now. +msg.room_upgraded = Research: {0} reaches level {1} – it works a quarter better, and it shows. msg.tool_research = Not researched yet - creatures studying in the library work on it. msg.script = {0} diff --git a/lang/es.lang b/lang/es.lang index 748ff15..f844fb9 100644 --- a/lang/es.lang +++ b/lang/es.lang @@ -931,6 +931,7 @@ research.now = Investigando: {0} ({1}%) research.next = después: {0} research.done = Aquí no queda nada por investigar. msg.researched = Investigado: ¡{0}! Ya está en el menú de construcción. +msg.room_upgraded = Investigación: {0} alcanza el nivel {1}: funciona un cuarto mejor, y se nota. msg.tool_research = Aún no investigado: las criaturas que estudian en la biblioteca trabajan en ello. msg.script = {0} diff --git a/lang/fi.lang b/lang/fi.lang index b2d8914..811e994 100644 --- a/lang/fi.lang +++ b/lang/fi.lang @@ -939,6 +939,7 @@ research.now = Tutkitaan: {0} ({1}%) research.next = sitten: {0} research.done = Täällä ei ole enää mitään tutkittavaa. msg.researched = Tutkittu: {0}! Se on nyt rakennusvalikossa. +msg.room_upgraded = Tutkimus: {0} saavuttaa tason {1} – toimii neljänneksen paremmin, ja sen näkee. msg.tool_research = Ei vielä tutkittu - kirjastossa opiskelevat olennot työstävät sitä. msg.script = {0} diff --git a/lang/hr.lang b/lang/hr.lang index eb4a9c2..3b22bb4 100644 --- a/lang/hr.lang +++ b/lang/hr.lang @@ -939,6 +939,7 @@ research.now = Istražuje se: {0} ({1}%) research.next = zatim: {0} research.done = Ovdje više nema što istraživati. msg.researched = Istraženo: {0}! Sad je u izborniku gradnje. +msg.room_upgraded = Istraživanje: {0} doseže razinu {1} – radi četvrtinu bolje, i to se vidi. msg.tool_research = Još nije istraženo - stvorenja koja uče u knjižnici rade na tome. msg.script = {0} diff --git a/lang/hu.lang b/lang/hu.lang index 22b63f5..596e816 100644 --- a/lang/hu.lang +++ b/lang/hu.lang @@ -939,6 +939,7 @@ research.now = Kutatás: {0} ({1}%) research.next = utána: {0} research.done = Itt már nincs mit kutatni. msg.researched = Kikutatva: {0}! Már az építési menüben van. +msg.room_upgraded = Kutatás: {0} eléri a(z) {1}. szintet – negyedével jobban működik, és látszik is. msg.tool_research = Még nincs kikutatva - a könyvtárban tanuló lények dolgoznak rajta. msg.script = {0} diff --git a/lang/it.lang b/lang/it.lang index 41d211e..3f84c84 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -931,6 +931,7 @@ research.now = In ricerca: {0} ({1}%) research.next = poi: {0} research.done = Qui non resta niente da ricercare. msg.researched = Ricerca completata: {0}! Ora è nel menu di costruzione. +msg.room_upgraded = Ricerca: {0} raggiunge il livello {1}: funziona un quarto meglio, e si vede. msg.tool_research = Non ancora ricercato - ci lavorano le creature che studiano in biblioteca. msg.script = {0} diff --git a/lang/nl.lang b/lang/nl.lang index ff21d84..9410430 100644 --- a/lang/nl.lang +++ b/lang/nl.lang @@ -939,6 +939,7 @@ research.now = Onderzoekt: {0} ({1}%) research.next = daarna: {0} research.done = Hier valt niets meer te onderzoeken. msg.researched = Onderzocht: {0}! Het staat nu in het bouwmenu. +msg.room_upgraded = Onderzoek: {0} bereikt niveau {1} – werkt een kwart beter, en dat zie je. msg.tool_research = Nog niet onderzocht - wezens die in de bibliotheek studeren, werken eraan. msg.script = {0} diff --git a/lang/no.lang b/lang/no.lang index f53708f..3a4e9fa 100644 --- a/lang/no.lang +++ b/lang/no.lang @@ -938,6 +938,7 @@ research.now = Forsker på: {0} ({1}%) research.next = deretter: {0} research.done = Ingenting mer å forske på her. msg.researched = Utforsket: {0}! Det finnes nå i byggemenyen. +msg.room_upgraded = Forskning: {0} når nivå {1} – virker en fjerdedel bedre, og det synes. msg.tool_research = Ikke utforsket ennå - skapninger som studerer i biblioteket, jobber med det. msg.script = {0} diff --git a/lang/pt.lang b/lang/pt.lang index f625877..fba953f 100644 --- a/lang/pt.lang +++ b/lang/pt.lang @@ -939,6 +939,7 @@ research.now = A investigar: {0} ({1}%) research.next = depois: {0} research.done = Já não há nada para investigar aqui. msg.researched = Investigado: {0}! Já está no menu de construção. +msg.room_upgraded = Investigação: {0} atinge o nível {1} – funciona um quarto melhor, e nota-se. msg.tool_research = Ainda não investigado - as criaturas que estudam na biblioteca estão a tratar disso. msg.script = {0} diff --git a/lang/ro.lang b/lang/ro.lang index e770ce7..2786f0d 100644 --- a/lang/ro.lang +++ b/lang/ro.lang @@ -939,6 +939,7 @@ research.now = Se cercetează: {0} ({1}%) research.next = apoi: {0} research.done = Nu mai e nimic de cercetat aici. msg.researched = Cercetat: {0}! E acum în meniul de construcție. +msg.room_upgraded = Cercetare: {0} atinge nivelul {1} – funcționează cu un sfert mai bine, și se vede. msg.tool_research = Încă necercetat - creaturile care studiază în bibliotecă lucrează la asta. msg.script = {0} diff --git a/lang/ru.lang b/lang/ru.lang index f388adf..a299e77 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -931,6 +931,7 @@ research.now = Исследуется: {0} ({1}%) research.next = затем: {0} research.done = Здесь больше нечего исследовать. msg.researched = Исследовано: {0}! Теперь это в меню строительства. +msg.room_upgraded = Исследование: {0} достигает уровня {1} — работает на четверть лучше, и это видно. msg.tool_research = Ещё не исследовано - над этим работают существа, что учатся в библиотеке. msg.script = {0} diff --git a/lang/sk.lang b/lang/sk.lang index 051dd9a..4367297 100644 --- a/lang/sk.lang +++ b/lang/sk.lang @@ -939,6 +939,7 @@ research.now = Skúma sa: {0} ({1} %) research.next = potom: {0} research.done = Tu už nezostáva nič na skúmanie. msg.researched = Preskúmané: {0}! Je to teraz v ponuke stavby. +msg.room_upgraded = Výskum: {0} dosahuje úroveň {1} – funguje o štvrtinu lepšie a je to vidieť. msg.tool_research = Ešte nepreskúmané - pracujú na tom tvory študujúce v knižnici. msg.script = {0} diff --git a/lang/sr.lang b/lang/sr.lang index ff1b7f0..c7a6adf 100644 --- a/lang/sr.lang +++ b/lang/sr.lang @@ -939,6 +939,7 @@ research.now = Истражује се: {0} ({1}%) research.next = затим: {0} research.done = Овде више нема шта да се истражи. msg.researched = Истражено: {0}! Сад је у менију градње. +msg.room_upgraded = Истраживање: {0} достиже ниво {1} – ради четвртину боље, и то се види. msg.tool_research = Још није истражено - створења која уче у библиотеци раде на томе. msg.script = {0} diff --git a/lang/sv.lang b/lang/sv.lang index 642d70b..f3ebf64 100644 --- a/lang/sv.lang +++ b/lang/sv.lang @@ -939,6 +939,7 @@ research.now = Forskar: {0} ({1}%) research.next = sedan: {0} research.done = Inget kvar att forska på här. msg.researched = Utforskat: {0}! Det finns nu i byggmenyn. +msg.room_upgraded = Forskning: {0} når nivå {1} – fungerar en fjärdedel bättre, och det syns. msg.tool_research = Inte utforskat än - varelser som studerar i biblioteket arbetar på det. msg.script = {0} diff --git a/src/game.rs b/src/game.rs index abcc841..2f94a8d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -486,6 +486,7 @@ pub struct MasterState { pub incoming: Vec<(u8, f32, Option)>, pub training_bill: f32, pub counts: [usize; N_ROOMS], + pub room_level: Vec, pub springs: usize, pub known: Vec, pub researchable: Vec, @@ -522,6 +523,8 @@ pub struct Game { pub hand: Vec, #[serde(skip)] pub counts: [usize; N_ROOMS], // room tiles per room kind (ROOM_KINDS order, recounted) + #[serde(default)] + pub room_level: Vec, // level of every room kind (ROOM_KINDS order; 1 when missing), researched pub gate_hp: f32, pub gate_hit_t: f32, pub gate_stage: u32, // defender thresholds triggered (0..3) @@ -783,6 +786,18 @@ pub fn room_index(k: Kind) -> Option { ROOM_KINDS.iter().position(|&r| r == k) } +impl Game { + // the researched level of a room kind of the master being processed (1, 2 or 3) + pub fn room_level_of(&self, k: Kind) -> u8 { + room_index(k).and_then(|r| self.room_level.get(r).copied()).unwrap_or(1).max(1) + } + + // how much better a room works at its level: +25% per level above the first + pub fn room_bonus(&self, k: Kind) -> f32 { + 1.0 + 0.25 * (self.room_level_of(k) - 1) as f32 + } +} + pub(crate) fn adj4(a: usize, b: usize) -> bool { let (ax, ay) = xy(a); let (bx, by) = xy(b); @@ -866,6 +881,7 @@ impl Game { paused: false, hand: Vec::new(), counts: [0; N_ROOMS], + room_level: Vec::new(), gate_hp: GATE_HP, gate_hit_t: -100.0, gate_stage: 0, @@ -1126,7 +1142,7 @@ impl Game { } pub fn gold_cap(&self) -> f32 { - CORE_STORE + TREASURY_TILE * self.counts[0] as f32 + CORE_STORE + TREASURY_TILE * self.counts[0] as f32 * self.room_bonus(Kind::Treasury) } pub fn add_gold(&mut self, amount: f32) -> f32 { @@ -1433,6 +1449,7 @@ impl Game { incoming: std::mem::take(&mut self.incoming), training_bill: self.training_bill, counts: self.counts, + room_level: self.room_level.clone(), springs: self.springs, known: std::mem::take(&mut self.known), researchable: std::mem::take(&mut self.researchable), @@ -1463,6 +1480,7 @@ impl Game { self.incoming = m.incoming; self.training_bill = m.training_bill; self.counts = m.counts; + self.room_level = m.room_level; self.springs = m.springs; self.known = m.known; self.researchable = m.researchable; @@ -1606,9 +1624,17 @@ impl Game { return; } self.time += dt; + // mushroom gardens grow faster at a higher level (of the master who owns them) + let farm = Kind::Farm; + let lv: Vec<(Owner, f32)> = self.master_sides().into_iter().map(|s| (Owner::of_side(s), { + let r = room_index(farm).unwrap_or(0); + let l = if s == self.me_side { self.room_level.get(r).copied() } else { self.masters.iter().find(|m| m.side == s).and_then(|m| m.room_level.get(r).copied()) }; + 1.0 + 0.25 * (l.unwrap_or(1).max(1) - 1) as f32 + })).collect(); for t in self.map.t.iter_mut() { if t.kind == Kind::Farm { - t.food = (t.food + dt / 18.0).min(3.0); + let f = lv.iter().find(|x| x.0 == t.owner).map_or(1.0, |x| x.1); + t.food = (t.food + dt * f / 18.0).min(3.0); } } for side in self.master_sides() { @@ -3798,8 +3824,9 @@ impl Game { } // resting heals: quickly in a bed room, slower next to the core; back to work at 75% let bed_room = matches!(self.map.t[b].kind, Kind::Lair | Kind::Aerie); - e.hp = (e.hp + e.max_hp * dt / if bed_room { 12.0 } else { 24.0 }).min(e.max_hp); - e.energy = (e.energy + dt / 14.0).min(1.0); + let rest = if bed_room { self.room_bonus(self.map.t[b].kind) } else { 1.0 }; + e.hp = (e.hp + e.max_hp * dt * rest / if bed_room { 12.0 } else { 24.0 }).min(e.max_hp); + e.energy = (e.energy + dt * rest / 14.0).min(1.0); if e.hp >= e.max_hp * 0.75 { e.job = Job::None; } @@ -3857,7 +3884,7 @@ impl Game { if tl.food >= 1.0 { tl.food -= 1.0; e.hunger = 0.0; - e.hp = (e.hp + e.max_hp * 0.2).min(e.max_hp); + e.hp = (e.hp + e.max_hp * 0.2 * self.room_bonus(Kind::Temple)).min(e.max_hp); } self.unreserve(me); e.job = Job::None; @@ -3887,7 +3914,7 @@ impl Game { } e.working = true; self.training_bill += TRAIN_COST * dt; - e.xp += 7.0 * dt; + e.xp += 7.0 * dt * self.room_bonus(Kind::Training); e.timer -= dt; if e.timer <= 0.0 { self.unreserve(me); @@ -3916,7 +3943,7 @@ impl Game { } e.working = true; let rate = if e.kind == EK::Cogling { 2.5 } else { 1.0 } * (1.0 + 0.1 * (e.level - 1) as f32); - self.forge_work += rate * dt; + self.forge_work += rate * dt * self.room_bonus(Kind::Forge); e.xp += 2.5 * dt; e.timer -= dt; if e.timer <= 0.0 { @@ -3939,7 +3966,7 @@ impl Game { EK::Owl => 1.3, _ => 0.5, } * (1.0 + 0.12 * (e.level - 1) as f32); - self.add_research(points * dt); + self.add_research(points * dt * self.room_bonus(Kind::Library)); e.xp += 3.0 * dt; e.timer -= dt; if e.timer <= 0.0 { diff --git a/src/map.rs b/src/map.rs index 2ae5b5a..bded888 100644 --- a/src/map.rs +++ b/src/map.rs @@ -352,6 +352,8 @@ pub struct Map { pub visible: Vec, // fog of war: tiles in sight right now #[serde(default, with = "crate::fog::bits")] pub sky: Vec, // tiles above ground (daylight, grass); empty: all underground + #[serde(default)] + pub upgrades: Vec<(u8, u8, u8)>, // researched room levels for the look: (master side, room index, level) } fn default_size() -> usize { @@ -604,7 +606,7 @@ impl Map { tl.hp = 6.0; } } - Map { w, h, t, core, portals, legacy_portal: None, gate, treasures, explored: Vec::new(), visible: Vec::new(), sky: Vec::new() } + Map { w, h, t, core, portals, legacy_portal: None, gate, treasures, explored: Vec::new(), visible: Vec::new(), sky: Vec::new(), upgrades: Vec::new() } } // Carves a meandering river; the player's area, the core and portals stay dry. diff --git a/src/render3d.rs b/src/render3d.rs index d1e9650..0ce655a 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -440,6 +440,15 @@ impl MB { } } +// the researched level of the room on tile t (1: none) +fn room_level_at(map: &Map, t: &Tile) -> u8 { + if map.upgrades.is_empty() { + return 1; + } + let (Some(r), side) = (room_index(t.kind), t.owner.side()) else { return 1 }; + map.upgrades.iter().find(|u| u.0 == side && u.1 as usize == r).map_or(1, |u| u.2) +} + // Ambient light field: own territory is lit, foreign areas are dim fn ambient_field(map: &Map) -> Vec { let mut f = vec![0.0f32; n()]; @@ -453,6 +462,11 @@ fn ambient_field(map: &Map) -> Vec { if map.sky(i) { f[i] = 1.6; } + // rooms of a higher level shine brighter + let lv = room_level_at(map, t); + if lv > 1 && f[i] > 0.0 { + f[i] += 0.25 * (lv - 1) as f32; + } if t.river() == Some(Kind::Lava) { f[i] = f[i].max(0.8); // lava lights up its surroundings } @@ -853,6 +867,26 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T if !solid_at(map, x - 1, z) && !outdoors { ln.strip(vec3(xf, 0.0, zf), vec3(xf, 0.0, zf + 1.0), Vec3::Y, OUTLINE_W, OUTLINE_C); } + // a room of a higher level: gold trim along its border (twice at level 3) + let lv = room_level_at(map, &t); + if lv > 1 { + let gold = col(vec3(1.0, 0.78, 0.25), 1.0); + for (d, &(dx, dz)) in DIR4.iter().enumerate() { + if map.get(x + dx, z + dz).map_or(false, |o| o.kind == t.kind && o.owner == t.owner) { + continue; + } + for k in 0..(lv - 1) { + let m = 0.08 + 0.12 * k as f32; + let (p, q) = match d { + 0 => (vec3(xf + m, 0.012, zf + m), vec3(xf + 1.0 - m, 0.012, zf + m)), + 1 => (vec3(xf + 1.0 - m, 0.012, zf + m), vec3(xf + 1.0 - m, 0.012, zf + 1.0 - m)), + 2 => (vec3(xf + 1.0 - m, 0.012, zf + 1.0 - m), vec3(xf + m, 0.012, zf + 1.0 - m)), + _ => (vec3(xf + m, 0.012, zf + 1.0 - m), vec3(xf + m, 0.012, zf + m)), + }; + ln.strip(p, q, Vec3::Y, OUTLINE_W * 1.5, gold); + } + } + } // room decor let dc = col(lava_lit(Vec3::splat(amb_at(amb, xf + 0.5, zf + 0.5)), lava_at(glow, xf + 0.5, zf + 0.5)), 1.0); if t.plant() { @@ -1083,6 +1117,10 @@ fn rgba(u: u32) -> Color { fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 { let mut h: u64 = 1469598103934665603; + // researched room levels change the light and the trim of every room + for u in &map.upgrades { + h = (h ^ (u.0 as u64 | (u.1 as u64) << 8 | (u.2 as u64) << 16)).wrapping_mul(1099511628211); + } for z in cz * CHUNK - 3..(cz + 1) * CHUNK + 3 { for x in cx * CHUNK - 3..(cx + 1) * CHUNK + 3 { let v = match map.get(x, z) { diff --git a/src/research.rs b/src/research.rs index 666754a..0d9f6d0 100644 --- a/src/research.rs +++ b/src/research.rs @@ -46,6 +46,26 @@ pub const RESEARCH: &[(usize, f32)] = &[ (51, 1000.0), // catapult ]; +// room levels, researched once everything else is known: (index in ROOM_KINDS, level, points) +pub const UPGRADES: &[(usize, u8, f32)] = &[ + (1, 2, 500.0), // lair + (2, 2, 500.0), // mushroom garden + (0, 2, 550.0), // treasury + (4, 2, 600.0), // library + (3, 2, 600.0), // training hall + (8, 2, 650.0), // forge + (6, 2, 650.0), // temple + (7, 2, 650.0), // aerie + (1, 3, 1000.0), // lair + (2, 3, 1000.0), // mushroom garden + (0, 3, 1100.0), // treasury + (4, 3, 1200.0), // library + (3, 3, 1200.0), // training hall + (8, 3, 1300.0), // forge + (6, 3, 1300.0), // temple + (7, 3, 1300.0), // aerie +]; + // known from the start in free play pub const BASE_TOOLS: &[usize] = &[0, 1, 2, 3, 4, 6, 7, 8, 9, 14, 17, 20, 45, 46, 49]; @@ -92,13 +112,45 @@ impl Game { !self.known(tool) && self.researchable.get(tool).copied().unwrap_or(false) && RESEARCH.iter().any(|&(t, _)| t == tool) } + // the room level being researched once no tool is left: index in UPGRADES + pub fn upgrade_target(&self) -> Option { + if self.research_target().is_some() || self.known.is_empty() { + return None; + } + UPGRADES.iter().position(|&(r, lv, _)| self.room_level.get(r).copied().unwrap_or(1).max(1) < lv) + } + pub(crate) fn add_research(&mut self, points: f32) { - if self.research_target().is_some() { + if self.research_target().is_some() || self.upgrade_target().is_some() { self.research += points; } } + // a room kind reached level `lv` (the map keeps it for the look of the rooms) + pub fn set_room_level(&mut self, r: usize, lv: u8) { + if self.room_level.len() < crate::game::N_ROOMS { + self.room_level.resize(crate::game::N_ROOMS, 1); + } + self.room_level[r] = lv; + let side = self.me_side; + self.map.upgrades.retain(|u| !(u.0 == side && u.1 as usize == r)); + if lv > 1 { + self.map.upgrades.push((side, r as u8, lv)); + } + } + pub(crate) fn update_research(&mut self) { + if let Some(u) = self.upgrade_target() { + let (r, lv, cost) = UPGRADES[u]; + if self.research >= cost { + self.research -= cost; + self.set_room_level(r, lv); + let roman = if lv == 2 { "II" } else { "III" }; + self.msg("msg.room_upgraded", vec![format!("@{}", crate::game::ROOM_NAMES[r]), roman.to_string()]); + self.sfx_ui(Sfx::LevelUp); + } + return; + } let Some(k) = self.research_target() else { return }; let (tool, cost) = RESEARCH[k]; if self.research >= cost { @@ -154,4 +206,23 @@ mod tests { assert!(g.can_research(10), "heal comes soon and can be researched"); assert!(!g.can_research(31), "magic doors are far away"); } + + #[test] + fn once_everything_is_known_the_library_raises_room_levels() { + let mut g = Game::new(9, 64, 64); + assert!(g.upgrade_target().is_none(), "tools first"); + for v in g.known.iter_mut() { + *v = true; + } + let u = g.upgrade_target().expect("a room level to research"); + let (r, lv, cost) = UPGRADES[u]; + assert_eq!(lv, 2); + let before = g.room_bonus(ROOM_KINDS[r]); + g.research = cost + 1.0; + g.update_research(); + assert_eq!(g.room_level_of(ROOM_KINDS[r]), 2); + assert!(g.room_bonus(ROOM_KINDS[r]) > before); + assert!(g.map.upgrades.iter().any(|x| x.1 as usize == r && x.2 == 2), "the map shows it"); + assert_ne!(g.upgrade_target(), Some(u), "the next one"); + } } diff --git a/src/ui.rs b/src/ui.rs index 2527e34..be2b9ba 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -667,6 +667,11 @@ impl Ui { let (tool, cost) = crate::research::RESEARCH[k]; let name: String = tool_info(TOOLS[tool]).name.chars().take(18).collect(); segs.push(vec![txt(tr("hud.research"), BLUE), Part::Gap, txt(name, TXT), Part::Gap, Part::Bar(g.research / cost, BLUE)]); + } else if let Some(u) = g.upgrade_target().filter(|_| g.counts[4] > 0) { + // everything known: the library works on room levels + let (r, lv, cost) = crate::research::UPGRADES[u]; + let name: String = format!("{} {}", tr(ROOM_NAMES[r]), if lv == 2 { "II" } else { "III" }).chars().take(20).collect(); + segs.push(vec![txt(tr("hud.research"), BLUE), Part::Gap, txt(name, TXT), Part::Gap, Part::Bar(g.research / cost, BLUE)]); } 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)]);