diff --git a/lang/cs.lang b/lang/cs.lang index 6d47d2a..c17c9fd 100644 --- a/lang/cs.lang +++ b/lang/cs.lang @@ -725,6 +725,9 @@ net.ai_off = Vyp. # rival masters (free game) newgame.difficulty = Soupeři +newgame.land = Terén +newgame.land0 = Pod zemí +newgame.land1 = Napůl na povrchu diff.0 = Lehká diff.1 = Střední diff.2 = Těžká @@ -1131,6 +1134,7 @@ ed.forts = Rytířské pevnosti ed.rivals = Soupeřící mistři ed.difficulty = Obtížnost ed.portals = Portály +ed.surface = Povrch (východ) ed.edit_map = Upravit mapu ed.make_random = Udělat náhodnou mapu ed.paint_map = Vygenerovat a kreslit mapu @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Rubínová žíla ed.terrain.diamond = Diamantová žíla ed.terrain.steel = Ocelová zeď ed.terrain.mushroom = Obří houba +ed.terrain.grass = Tráva (povrch) +ed.terrain.tree = Strom ed.place.core = Jádro duše ed.place.portal = Portál ed.place.gate = Brána hrdinů diff --git a/lang/da.lang b/lang/da.lang index de30840..26d7fe1 100644 --- a/lang/da.lang +++ b/lang/da.lang @@ -724,6 +724,9 @@ net.ai_off = Fra # rival masters (free game) newgame.difficulty = Modstandere +newgame.land = Terræn +newgame.land0 = Under jorden +newgame.land1 = Halvt over jorden diff.0 = Let diff.1 = Middel diff.2 = Svær @@ -1130,6 +1133,7 @@ ed.forts = Ridderfæstninger ed.rivals = Rivaliserende mestre ed.difficulty = Sværhedsgrad ed.portals = Portaler +ed.surface = Over jorden (øst) ed.edit_map = Rediger kortet ed.make_random = Gør det til et tilfældigt kort ed.paint_map = Generer og mal kortet @@ -1210,6 +1214,8 @@ ed.terrain.ruby = Rubinåre ed.terrain.diamond = Diamantåre ed.terrain.steel = Stålmur ed.terrain.mushroom = Kæmpesvamp +ed.terrain.grass = Græs (over jorden) +ed.terrain.tree = Træ ed.place.core = Sjælekerne ed.place.portal = Portal ed.place.gate = Helteport diff --git a/lang/de.lang b/lang/de.lang index c95c1c7..8bc1a89 100644 --- a/lang/de.lang +++ b/lang/de.lang @@ -724,6 +724,9 @@ net.ai_off = Aus # rival masters (free game) newgame.difficulty = Gegner +newgame.land = Gelände +newgame.land0 = Unterirdisch +newgame.land1 = Halb über der Erde diff.0 = Leicht diff.1 = Mittel diff.2 = Schwer @@ -1130,6 +1133,7 @@ ed.forts = Ritterfestungen ed.rivals = Rivalen-Meister ed.difficulty = Schwierigkeit ed.portals = Portale +ed.surface = Oberwelt (Osten) ed.edit_map = Karte bearbeiten ed.make_random = Zur Zufallskarte machen ed.paint_map = Karte erzeugen und malen @@ -1210,6 +1214,8 @@ ed.terrain.ruby = Rubinader ed.terrain.diamond = Diamantader ed.terrain.steel = Stahlwand ed.terrain.mushroom = Riesenpilz +ed.terrain.grass = Gras (Oberwelt) +ed.terrain.tree = Baum ed.place.core = Seelenkern ed.place.portal = Portal ed.place.gate = Heldentor diff --git a/lang/el.lang b/lang/el.lang index f35f532..19f1652 100644 --- a/lang/el.lang +++ b/lang/el.lang @@ -725,6 +725,9 @@ net.ai_off = Όχι # rival masters (free game) newgame.difficulty = Αντίπαλοι +newgame.land = Έδαφος +newgame.land0 = Υπόγεια +newgame.land1 = Μισό στην επιφάνεια diff.0 = Εύκολο diff.1 = Μέτριο diff.2 = Δύσκολο @@ -1131,6 +1134,7 @@ ed.forts = Ιπποτικά φρούρια ed.rivals = Αντίπαλοι άρχοντες ed.difficulty = Δυσκολία ed.portals = Πύλες μετάβασης +ed.surface = Επιφάνεια (ανατολή) ed.edit_map = Επεξεργασία χάρτη ed.make_random = Κάν' τον τυχαίο χάρτη ed.paint_map = Δημιουργία και ζωγραφική χάρτη @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Φλέβα ρουμπινιού ed.terrain.diamond = Φλέβα διαμαντιού ed.terrain.steel = Ατσάλινος τοίχος ed.terrain.mushroom = Γιγάντιο μανιτάρι +ed.terrain.grass = Γρασίδι (επιφάνεια) +ed.terrain.tree = Δέντρο ed.place.core = Πυρήνας ψυχής ed.place.portal = Πύλη μετάβασης ed.place.gate = Πύλη ηρώων diff --git a/lang/en.lang b/lang/en.lang index fb34a0d..b5cca69 100644 --- a/lang/en.lang +++ b/lang/en.lang @@ -725,6 +725,9 @@ net.ai_off = Off # rival masters (free game) newgame.difficulty = Opponents +newgame.land = Land +newgame.land0 = Underground +newgame.land1 = Half above ground diff.0 = Easy diff.1 = Medium diff.2 = Hard @@ -1131,6 +1134,7 @@ ed.forts = Knight fortresses ed.rivals = Rival masters ed.difficulty = Difficulty ed.portals = Portals +ed.surface = Above ground (east) ed.edit_map = Edit the map ed.make_random = Make it a random map ed.paint_map = Generate and paint the map @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Ruby vein ed.terrain.diamond = Diamond vein ed.terrain.steel = Steel wall ed.terrain.mushroom = Giant mushroom +ed.terrain.grass = Grass (above ground) +ed.terrain.tree = Tree ed.place.core = Soul core ed.place.portal = Portal ed.place.gate = Hero gate diff --git a/lang/es.lang b/lang/es.lang index 41e93b7..be97978 100644 --- a/lang/es.lang +++ b/lang/es.lang @@ -717,6 +717,9 @@ net.ai_off = No # rival masters (free game) newgame.difficulty = Rivales +newgame.land = Terreno +newgame.land0 = Subterráneo +newgame.land1 = Mitad en superficie diff.0 = Fácil diff.1 = Normal diff.2 = Difícil @@ -1123,6 +1126,7 @@ ed.forts = Fortalezas ed.rivals = Señores rivales ed.difficulty = Dificultad ed.portals = Portales +ed.surface = Superficie (este) ed.edit_map = Editar el mapa ed.make_random = Hacerlo aleatorio ed.paint_map = Generar y pintar el mapa @@ -1203,6 +1207,8 @@ ed.terrain.ruby = Veta de rubíes ed.terrain.diamond = Veta de diamantes ed.terrain.steel = Muro de acero ed.terrain.mushroom = Seta gigante +ed.terrain.grass = Hierba (superficie) +ed.terrain.tree = Árbol ed.place.core = Núcleo de alma ed.place.portal = Portal ed.place.gate = Puerta de héroes diff --git a/lang/fi.lang b/lang/fi.lang index a9b6bd0..009dfc9 100644 --- a/lang/fi.lang +++ b/lang/fi.lang @@ -725,6 +725,9 @@ net.ai_off = Pois # rival masters (free game) newgame.difficulty = Vastustajat +newgame.land = Maasto +newgame.land0 = Maan alla +newgame.land1 = Puoliksi maan päällä diff.0 = Helppo diff.1 = Keski diff.2 = Vaikea @@ -1131,6 +1134,7 @@ ed.forts = Ritarilinnat ed.rivals = Kilpailijat ed.difficulty = Vaikeus ed.portals = Portaalit +ed.surface = Maan päällä (itä) ed.edit_map = Muokkaa karttaa ed.make_random = Tee siitä satunnaiskartta ed.paint_map = Luo ja piirrä kartta @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Rubiinisuoni ed.terrain.diamond = Timanttisuoni ed.terrain.steel = Teräsmuuri ed.terrain.mushroom = Jättisieni +ed.terrain.grass = Ruoho (maan päällä) +ed.terrain.tree = Puu ed.place.core = Sieluydin ed.place.portal = Portaali ed.place.gate = Sankariportti diff --git a/lang/hr.lang b/lang/hr.lang index b8f06cd..69bf21b 100644 --- a/lang/hr.lang +++ b/lang/hr.lang @@ -725,6 +725,9 @@ net.ai_off = Isklj. # rival masters (free game) newgame.difficulty = Protivnici +newgame.land = Teren +newgame.land0 = Pod zemljom +newgame.land1 = Pola na površini diff.0 = Lako diff.1 = Srednje diff.2 = Teško @@ -1131,6 +1134,7 @@ ed.forts = Viteške tvrđave ed.rivals = Suparnici ed.difficulty = Težina ed.portals = Portali +ed.surface = Površina (istok) ed.edit_map = Uredi kartu ed.make_random = Pretvori u slučajnu kartu ed.paint_map = Generiraj i nacrtaj kartu @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Žila rubina ed.terrain.diamond = Žila dijamanata ed.terrain.steel = Čelični zid ed.terrain.mushroom = Divovska gljiva +ed.terrain.grass = Trava (površina) +ed.terrain.tree = Stablo ed.place.core = Jezgra duše ed.place.portal = Portal ed.place.gate = Vrata junaka diff --git a/lang/hu.lang b/lang/hu.lang index 5040046..325070c 100644 --- a/lang/hu.lang +++ b/lang/hu.lang @@ -725,6 +725,9 @@ net.ai_off = Ki # rival masters (free game) newgame.difficulty = Ellenfelek +newgame.land = Terep +newgame.land0 = Föld alatt +newgame.land1 = Félig a felszínen diff.0 = Könnyű diff.1 = Közepes diff.2 = Nehéz @@ -1131,6 +1134,7 @@ ed.forts = Lovagerődök ed.rivals = Rivális mesterek ed.difficulty = Nehézség ed.portals = Portálok +ed.surface = Felszín (kelet) ed.edit_map = Térkép szerkesztése ed.make_random = Legyen véletlen térkép ed.paint_map = Térkép generálása és festése @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Rubinér ed.terrain.diamond = Gyémántér ed.terrain.steel = Acélfal ed.terrain.mushroom = Óriásgomba +ed.terrain.grass = Fű (felszín) +ed.terrain.tree = Fa ed.place.core = Lélekmag ed.place.portal = Portál ed.place.gate = Hőskapu diff --git a/lang/it.lang b/lang/it.lang index 509148a..8b2dca8 100644 --- a/lang/it.lang +++ b/lang/it.lang @@ -717,6 +717,9 @@ net.ai_off = No # rival masters (free game) newgame.difficulty = Avversari +newgame.land = Terreno +newgame.land0 = Sotterraneo +newgame.land1 = Metà in superficie diff.0 = Facile diff.1 = Medio diff.2 = Difficile @@ -1123,6 +1126,7 @@ ed.forts = Fortezze ed.rivals = Maestri rivali ed.difficulty = Difficoltà ed.portals = Portali +ed.surface = Superficie (est) ed.edit_map = Modifica mappa ed.make_random = Rendila casuale ed.paint_map = Genera e dipingi la mappa @@ -1203,6 +1207,8 @@ ed.terrain.ruby = Vena di rubini ed.terrain.diamond = Vena di diamanti ed.terrain.steel = Muro d'acciaio ed.terrain.mushroom = Fungo gigante +ed.terrain.grass = Erba (superficie) +ed.terrain.tree = Albero ed.place.core = Nucleo dell'Anima ed.place.portal = Portale ed.place.gate = Porta degli Eroi diff --git a/lang/nl.lang b/lang/nl.lang index 9549ac1..f4f6d4b 100644 --- a/lang/nl.lang +++ b/lang/nl.lang @@ -725,6 +725,9 @@ net.ai_off = Uit # rival masters (free game) newgame.difficulty = Tegenstanders +newgame.land = Terrein +newgame.land0 = Ondergronds +newgame.land1 = Half bovengronds diff.0 = Makkelijk diff.1 = Middel diff.2 = Moeilijk @@ -1131,6 +1134,7 @@ ed.forts = Riddervestingen ed.rivals = Rivalen ed.difficulty = Moeilijkheid ed.portals = Portalen +ed.surface = Bovengronds (oosten) ed.edit_map = Kaart bewerken ed.make_random = Maak er een willekeurige kaart van ed.paint_map = Kaart genereren en tekenen @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Robijnader ed.terrain.diamond = Diamantader ed.terrain.steel = Stalen muur ed.terrain.mushroom = Reuzenpaddenstoel +ed.terrain.grass = Gras (bovengronds) +ed.terrain.tree = Boom ed.place.core = Zielenkern ed.place.portal = Portaal ed.place.gate = Heldenpoort diff --git a/lang/no.lang b/lang/no.lang index 82fb639..93560b4 100644 --- a/lang/no.lang +++ b/lang/no.lang @@ -724,6 +724,9 @@ net.ai_off = Av # rival masters (free game) newgame.difficulty = Motstandere +newgame.land = Terreng +newgame.land0 = Under jorden +newgame.land1 = Halvt over jorden diff.0 = Lett diff.1 = Middels diff.2 = Vanskelig @@ -1130,6 +1133,7 @@ ed.forts = Ridderfestninger ed.rivals = Rivaliserende mestere ed.difficulty = Vanskelighetsgrad ed.portals = Portaler +ed.surface = Over jorden (øst) ed.edit_map = Rediger kartet ed.make_random = Gjør det til et tilfeldig kart ed.paint_map = Generer og mal kartet @@ -1210,6 +1214,8 @@ ed.terrain.ruby = Rubinåre ed.terrain.diamond = Diamantåre ed.terrain.steel = Stålvegg ed.terrain.mushroom = Kjempesopp +ed.terrain.grass = Gress (over jorden) +ed.terrain.tree = Tre ed.place.core = Sjelekjerne ed.place.portal = Portal ed.place.gate = Helteport diff --git a/lang/pt.lang b/lang/pt.lang index 42fdb97..e39a620 100644 --- a/lang/pt.lang +++ b/lang/pt.lang @@ -725,6 +725,9 @@ net.ai_off = Desl. # rival masters (free game) newgame.difficulty = Adversários +newgame.land = Terreno +newgame.land0 = Subterrâneo +newgame.land1 = Metade à superfície diff.0 = Fácil diff.1 = Médio diff.2 = Difícil @@ -1131,6 +1134,7 @@ ed.forts = Fortalezas ed.rivals = Mestres rivais ed.difficulty = Dificuldade ed.portals = Portais +ed.surface = Superfície (leste) ed.edit_map = Editar o mapa ed.make_random = Tornar mapa aleatório ed.paint_map = Gerar e pintar o mapa @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Veio de rubis ed.terrain.diamond = Veio diamantes ed.terrain.steel = Parede de aço ed.terrain.mushroom = Cogumelo gigante +ed.terrain.grass = Relva (superfície) +ed.terrain.tree = Árvore ed.place.core = Núcleo de alma ed.place.portal = Portal ed.place.gate = Portão heróis diff --git a/lang/ro.lang b/lang/ro.lang index 6eb1935..614925b 100644 --- a/lang/ro.lang +++ b/lang/ro.lang @@ -725,6 +725,9 @@ net.ai_off = Oprit # rival masters (free game) newgame.difficulty = Adversari +newgame.land = Teren +newgame.land0 = Subteran +newgame.land1 = Jumătate la suprafață diff.0 = Ușor diff.1 = Mediu diff.2 = Greu @@ -1131,6 +1134,7 @@ ed.forts = Fortărețe cavalerești ed.rivals = Stăpâni rivali ed.difficulty = Dificultate ed.portals = Portaluri +ed.surface = Suprafață (est) ed.edit_map = Editează harta ed.make_random = Fă-o hartă aleatoare ed.paint_map = Generează și pictează harta @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Filon de rubine ed.terrain.diamond = Filon de diamante ed.terrain.steel = Zid de oțel ed.terrain.mushroom = Ciupercă uriașă +ed.terrain.grass = Iarbă (suprafață) +ed.terrain.tree = Copac ed.place.core = Miezul sufletului ed.place.portal = Portal ed.place.gate = Poarta eroilor diff --git a/lang/ru.lang b/lang/ru.lang index 634198c..60ca882 100644 --- a/lang/ru.lang +++ b/lang/ru.lang @@ -717,6 +717,9 @@ net.ai_off = Выкл # rival masters (free game) newgame.difficulty = Противники +newgame.land = Местность +newgame.land0 = Под землёй +newgame.land1 = Наполовину наверху diff.0 = Легко diff.1 = Средне diff.2 = Сложно @@ -1123,6 +1126,7 @@ ed.forts = Крепости ed.rivals = Соперники ed.difficulty = Сложность ed.portals = Порталы +ed.surface = Поверхность (восток) ed.edit_map = Изменить карту ed.make_random = Сделать случайной ed.paint_map = Создать и нарисовать карту @@ -1203,6 +1207,8 @@ ed.terrain.ruby = Рубиновая жила ed.terrain.diamond = Алмазная жила ed.terrain.steel = Стальная стена ed.terrain.mushroom = Гигантский гриб +ed.terrain.grass = Трава (поверхность) +ed.terrain.tree = Дерево ed.place.core = Ядро души ed.place.portal = Портал ed.place.gate = Врата героев diff --git a/lang/sk.lang b/lang/sk.lang index f89e5da..6842761 100644 --- a/lang/sk.lang +++ b/lang/sk.lang @@ -725,6 +725,9 @@ net.ai_off = Vyp. # rival masters (free game) newgame.difficulty = Súperi +newgame.land = Terén +newgame.land0 = Pod zemou +newgame.land1 = Napoly na povrchu diff.0 = Ľahká diff.1 = Stredná diff.2 = Ťažká @@ -1131,6 +1134,7 @@ ed.forts = Rytierske pevnosti ed.rivals = Súperiaci majstri ed.difficulty = Obťažnosť ed.portals = Portály +ed.surface = Povrch (východ) ed.edit_map = Upraviť mapu ed.make_random = Urobiť náhodnú mapu ed.paint_map = Vygenerovať a kresliť mapu @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Rubínová žila ed.terrain.diamond = Diamantová žila ed.terrain.steel = Oceľová stena ed.terrain.mushroom = Obria huba +ed.terrain.grass = Tráva (povrch) +ed.terrain.tree = Strom ed.place.core = Jadro duše ed.place.portal = Portál ed.place.gate = Brána hrdinov diff --git a/lang/sr.lang b/lang/sr.lang index e2ae196..6984023 100644 --- a/lang/sr.lang +++ b/lang/sr.lang @@ -725,6 +725,9 @@ net.ai_off = Иск. # rival masters (free game) newgame.difficulty = Противници +newgame.land = Терен +newgame.land0 = Под земљом +newgame.land1 = Пола на површини diff.0 = Лако diff.1 = Средње diff.2 = Тешко @@ -1131,6 +1134,7 @@ ed.forts = Витешке тврђаве ed.rivals = Супарници ed.difficulty = Тежина ed.portals = Портали +ed.surface = Површина (исток) ed.edit_map = Уреди мапу ed.make_random = Претвори у случајну мапу ed.paint_map = Генериши и нацртај мапу @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Жила рубина ed.terrain.diamond = Жила дијаманата ed.terrain.steel = Челични зид ed.terrain.mushroom = Џиновска печурка +ed.terrain.grass = Трава (површина) +ed.terrain.tree = Стабло ed.place.core = Језгро душе ed.place.portal = Портал ed.place.gate = Капија хероја diff --git a/lang/sv.lang b/lang/sv.lang index 2088f72..2ee8f19 100644 --- a/lang/sv.lang +++ b/lang/sv.lang @@ -725,6 +725,9 @@ net.ai_off = Av # rival masters (free game) newgame.difficulty = Motståndare +newgame.land = Terräng +newgame.land0 = Under jord +newgame.land1 = Halvt ovan jord diff.0 = Lätt diff.1 = Medel diff.2 = Svår @@ -1131,6 +1134,7 @@ ed.forts = Riddarfästningar ed.rivals = Rivaliserande mästare ed.difficulty = Svårighetsgrad ed.portals = Portaler +ed.surface = Ovan jord (öster) ed.edit_map = Redigera kartan ed.make_random = Gör den till slumpkarta ed.paint_map = Generera och måla kartan @@ -1211,6 +1215,8 @@ ed.terrain.ruby = Rubinåder ed.terrain.diamond = Diamantåder ed.terrain.steel = Stålvägg ed.terrain.mushroom = Jättesvamp +ed.terrain.grass = Gräs (ovan jord) +ed.terrain.tree = Träd ed.place.core = Själskärna ed.place.portal = Portal ed.place.gate = Hjälteport diff --git a/src/campaign.rs b/src/campaign.rs index 134e94a..9f61a05 100644 --- a/src/campaign.rs +++ b/src/campaign.rs @@ -48,6 +48,7 @@ pub struct MissionDef { pub rivals: Vec<(f32, f32, u8, u8)>, // rival masters: position (fraction of the map), difficulty, side (2 Mossgrim, 3 Lady Nightwell) pub islands: Vec, pub caves: Vec, + pub surface: f32, // share of the width from where the map lies above ground (0: none) } // an island in a lake of water or lava, with treasure and/or a mana spring @@ -166,6 +167,7 @@ fn base(key: &str, size: (usize, usize), seed: u32, goals: Vec) -> Mission rivals: Vec::new(), islands: Vec::new(), caves: Vec::new(), + surface: 0.0, } } @@ -181,6 +183,7 @@ pub fn mission_def_from_doc(d: &crate::campaign_doc::MissionDoc) -> MissionDef { out.gate = d.waves; out.gate_defenders = d.waves; out.time_limit = d.time_limit; + out.surface = if d.gen.surface { 0.5 } else { 0.0 }; out } @@ -537,6 +540,9 @@ fn start_mission_def(d: &MissionDef, i: usize, carry: Option<&Carry>, coop: bool } g.place_dwarf_caves(d.seed); g.add_wilds(d.seed, false); + if d.surface > 0.0 { + g.add_surface(d.seed, d.surface, 2 + (h / 64) as u32); + } g.init_research(Some(i)); g.init_fog(); let heart = d.gate || d.forts.iter().any(|f| f.heart); diff --git a/src/campaign_doc.rs b/src/campaign_doc.rs index 6bbb07d..9349936 100644 --- a/src/campaign_doc.rs +++ b/src/campaign_doc.rs @@ -145,11 +145,13 @@ pub struct GenDoc { pub rivals: u8, // 0..2 rival masters #[serde(default = "default_difficulty")] pub difficulty: u8, // 0 easy, 1 medium, 2 hard + #[serde(default)] + pub surface: bool, // the eastern half lies above ground (forest) } impl Default for GenDoc { fn default() -> Self { - GenDoc { rivers: 1, lava: 0, forts: 1, rivals: 0, difficulty: 1 } + GenDoc { rivers: 1, lava: 0, forts: 1, rivals: 0, difficulty: 1, surface: false } } } diff --git a/src/editor/coop_campaign.rs b/src/editor/coop_campaign.rs index 44a9783..853e26a 100644 --- a/src/editor/coop_campaign.rs +++ b/src/editor/coop_campaign.rs @@ -91,7 +91,7 @@ pub fn coop_campaign() -> CampaignDoc { waves: sp.waves.0, wave_limit: sp.waves.1, first_wave: if sp.waves.0 { sp.waves.2 } else { 300.0 }, - gen: GenDoc { rivers: sp.gen.0, lava: sp.gen.1, forts: sp.gen.2, rivals: 0, difficulty: sp.gen.3 }, + gen: GenDoc { rivers: sp.gen.0, lava: sp.gen.1, forts: sp.gen.2, rivals: 0, difficulty: sp.gen.3, surface: false }, next: (i + 1 < specs.len()).then(|| format!("c{:02}", i + 2)), pos: (40.0 + (i % 5) as f32 * 220.0, 40.0 + (i / 5) as f32 * 140.0), ..MissionDoc::default() diff --git a/src/editor/map.rs b/src/editor/map.rs index 0f000e5..328afbf 100644 --- a/src/editor/map.rs +++ b/src/editor/map.rs @@ -35,7 +35,7 @@ enum Cat { const CATS: [Cat; 9] = [Cat::Terrain, Cat::Rooms, Cat::Places, Cat::Units, Cat::Towers, Cat::Traps, Cat::Forts, Cat::Zones, Cat::Erase]; -const TERRAIN: [&str; 14] = ["floor", "earth", "reinforced", "rock", "water", "lava", "bridge", "lavabridge", "gold", "silver", "ruby", "diamond", "steel", "mushroom"]; +const TERRAIN: [&str; 16] = ["floor", "earth", "reinforced", "rock", "water", "lava", "bridge", "lavabridge", "gold", "silver", "ruby", "diamond", "steel", "mushroom", "grass", "tree"]; const PLACES: [&str; 7] = ["core", "portal", "gate", "spring", "shard", "chest", "chained"]; const OWNERS: [Owner; 6] = [Owner::Player, Owner::Hero, Owner::Rival1, Owner::Rival2, Owner::Rival3, Owner::None]; const ZONE_SHAPES: [&str; 4] = ["rect", "circle", "tiles", "line"]; @@ -76,6 +76,8 @@ fn kind_of_terrain(t: &str) -> (Kind, bool) { "bridge" => (Kind::Bridge, false), "lavabridge" => (Kind::LavaBridge, false), "mushroom" => (Kind::Mushroom, false), + "grass" => (Kind::Floor, false), + "tree" => (Kind::Tree, false), "gold" => (Kind::Gold, false), "silver" => (Kind::Silver, false), "ruby" => (Kind::Ruby, false), @@ -209,7 +211,10 @@ impl MapEdit { } match self.cat { Cat::Terrain => { - let (k, reinforced) = kind_of_terrain(TERRAIN[self.item.min(TERRAIN.len() - 1)]); + let name = TERRAIN[self.item.min(TERRAIN.len() - 1)]; + let (k, reinforced) = kind_of_terrain(name); + // grass and trees lie above ground; anything else is painted underground + self.game.map.set_sky(i, matches!(name, "grass" | "tree")); self.game.torches.retain(|tr| tr.wall != i); let owner = if k == Kind::Floor || reinforced || matches!(k, Kind::Bridge | Kind::LavaBridge) { OWNERS[self.owner] } else { Owner::None }; let tl = &mut self.game.map.t[i]; @@ -218,7 +223,7 @@ impl MapEdit { tl.owner = if reinforced && owner == Owner::None { Owner::Player } else { owner }; tl.marked = false; if tl.plant() { - tl.hp = crate::wood::MUSHROOM_HP; + tl.hp = if k == Kind::Tree { crate::wood::TREE_HP } else { crate::wood::MUSHROOM_HP }; } tl.gold = match k { Kind::Gold => 1200, diff --git a/src/editor/mod.rs b/src/editor/mod.rs index b884b37..286f2e7 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -777,6 +777,7 @@ impl Editor { (tr("ed.rivals"), m.gen.rivals.to_string(), 6), (tr("ed.difficulty"), tr(["diff.0", "diff.1", "diff.2"][m.gen.difficulty.min(2) as usize]), 7), (tr("ed.portals"), m.portals.to_string(), 8), + (tr("ed.surface"), tr(if m.gen.surface { "val.on" } else { "val.off" }), 9), ]; if fixed { rows.truncate(0); @@ -794,6 +795,7 @@ impl Editor { 5 => m.gen.forts = (m.gen.forts as i32 + d.signum()).clamp(0, 3) as u8, 6 => m.gen.rivals = (m.gen.rivals as i32 + d.signum()).clamp(0, 2) as u8, 7 => m.gen.difficulty = (m.gen.difficulty as i32 + d.signum()).clamp(0, 2) as u8, + 9 => m.gen.surface = !m.gen.surface, _ => m.portals = (m.portals as i32 + d.signum()).clamp(0, 6) as usize, } } diff --git a/src/editor/templates.rs b/src/editor/templates.rs index fcb183e..28d604e 100644 --- a/src/editor/templates.rs +++ b/src/editor/templates.rs @@ -93,7 +93,7 @@ pub fn demo_campaign() -> CampaignDoc { seed: 4242, gold: 4000.0, goals: vec!["Towers".into()], - gen: GenDoc { rivers: 1, lava: 0, forts: 1, rivals: 0, difficulty: 0 }, + gen: GenDoc { rivers: 1, lava: 0, forts: 1, rivals: 0, difficulty: 0, surface: false }, next: Some("demo2".into()), branches: vec![BranchDoc { id: "rich".into(), goal: "Loot(2000)".into(), title: tr("demo.branch"), next: "demo2".into(), priority: Some(1) }], pos: (40.0, 60.0), @@ -226,7 +226,7 @@ pub fn demo_campaign() -> CampaignDoc { seed: 77, gold: 3000.0, goals: vec!["KillAll".into()], - gen: GenDoc { rivers: 1, lava: 1, forts: 1, rivals: 0, difficulty: 0 }, + gen: GenDoc { rivers: 1, lava: 1, forts: 1, rivals: 0, difficulty: 0, surface: false }, pos: (300.0, 60.0), ..MissionDoc::default() }; diff --git a/src/lobby.rs b/src/lobby.rs index 40762ce..548e76e 100644 --- a/src/lobby.rs +++ b/src/lobby.rs @@ -788,7 +788,7 @@ mod tests { use crate::blocks::{Block, Val}; use crate::campaign_doc::*; let mut doc = CampaignDoc { name: "Coop test".into(), ..CampaignDoc::default() }; - let mut m = MissionDoc { key: "c1".into(), title: "Random".into(), size: (80, 80), seed: 5, goals: vec!["KillAll".into(), "Rivals".into()], waves: true, first_wave: 3.0, gen: GenDoc { rivers: 1, lava: 1, forts: 1, rivals: 1, difficulty: 1 }, ..MissionDoc::default() }; + let mut m = MissionDoc { key: "c1".into(), title: "Random".into(), size: (80, 80), seed: 5, goals: vec!["KillAll".into(), "Rivals".into()], waves: true, first_wave: 3.0, gen: GenDoc { rivers: 1, lava: 1, forts: 1, rivals: 1, difficulty: 1, surface: false }, ..MissionDoc::default() }; m.scripts.start = vec![Block::Gold { amount: Val::num(250.0) }]; m.scripts.triggers = vec![TriggerDoc { name: "wave".into(), kind: TriggerKind::Timer { secs: 2.0, every: true }, once: false, enabled: true, script: vec![Block::Wave { count: Val::num(1.0) }] }]; doc.missions.push(m.clone()); diff --git a/src/main.rs b/src/main.rs index 9143641..841f296 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,7 @@ mod sfx; mod tracks; mod traps; mod sound; +mod surface; mod spells; mod touch; mod ui; @@ -173,7 +174,12 @@ fn finish_rect(app: &mut App, tool: Tool, start: (i32, i32), mx: f32, my: f32) { } fn new_game(seed: u32, s: &Settings) -> (Game, Cam) { - let g = Game::new_free(seed, s.map_w, s.map_h, s.difficulty); + let mut g = Game::new_free(seed, s.map_w, s.map_h, s.difficulty); + if s.surface { + // the eastern half above ground: forest, paths, cave mouths + g.add_surface(seed, 0.5, 2 + (s.map_h / 64) as u32); + g.init_fog(); + } let (cx, cy) = g.map.core; let cam = Cam::new(cx as f32 + 1.5, cy as f32 + 1.5); (g, cam) diff --git a/src/map.rs b/src/map.rs index b1e998e..2ae5b5a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -350,6 +350,8 @@ pub struct Map { pub explored: Vec, // fog of war: tiles the dungeon knows (empty: no fog) #[serde(skip)] 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 } fn default_size() -> usize { @@ -367,6 +369,21 @@ impl Map { } } + // tile i lies above ground + pub fn sky(&self, i: usize) -> bool { + self.sky.get(i).copied().unwrap_or(false) + } + + pub fn set_sky(&mut self, i: usize, on: bool) { + if self.sky.len() != self.t.len() { + if !on { + return; + } + self.sky = vec![false; self.t.len()]; + } + self.sky[i] = on; + } + pub fn get(&self, x: i32, y: i32) -> Option<&Tile> { if inb(x, y) { Some(&self.t[idx(x, y)]) @@ -587,7 +604,7 @@ impl Map { tl.hp = 6.0; } } - Map { w, h, t, core, portals, legacy_portal: None, gate, treasures, explored: Vec::new(), visible: Vec::new() } + Map { w, h, t, core, portals, legacy_portal: None, gate, treasures, explored: Vec::new(), visible: Vec::new(), sky: Vec::new() } } // Carves a meandering river; the player's area, the core and portals stay dry. diff --git a/src/menu.rs b/src/menu.rs index 42f4331..037aafe 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -230,6 +230,7 @@ enum Btn { Tab(usize), Change(usize, i32), MapW(i32), + Land, MapH(i32), Difficulty(i32), Start, @@ -675,6 +676,10 @@ impl Menu { settings.difficulty = (settings.difficulty as i32 + d).rem_euclid(3) as u8; MenuAction::SettingsChanged } + Some(Btn::Land) => { + settings.surface = !settings.surface; + MenuAction::SettingsChanged + } Some(Btn::MapH(d)) => { settings.map_h = (settings.map_h as i32 + d * 32).clamp(MIN_SIZE as i32, MAX_SIZE as i32) as usize; MenuAction::SettingsChanged @@ -1153,27 +1158,30 @@ impl Menu { let sh = sh(); let (mx, my) = mouse(); let cols = 48.min((sw / CW()) as i32); - let rows = 22; + let rows = 25; let x = grid_center(sw, cols as f32 * CW()).max(0.0); let y = (grid_center_y(sh, rows as f32 * CH())).max(0.0); gfx.window_titled(x, y, cols, rows, S(), true, FRAME_HI, Color::new(0.05, 0.03, 0.04, 0.97), &tr("menu.new"), FRAME_HI); let diff = tr(&format!("diff.{}", s.difficulty.min(2))); - let lines = [("newgame.width", s.map_w.to_string(), 0), ("newgame.height", s.map_h.to_string(), 1), ("newgame.difficulty", diff, 2)]; + let land = tr(if s.surface { "newgame.land1" } else { "newgame.land0" }); + let lines = [("newgame.width", s.map_w.to_string(), 0), ("newgame.height", s.map_h.to_string(), 1), ("newgame.difficulty", diff, 2), ("newgame.land", land, 3)]; for (k, (key, val, which)) in lines.iter().enumerate() { let (key, which) = (*key, *which); let by = y + CH() * (2.0 + k as f32 * 2.5); gfx.text(&tr(key), x + CW() * 3.0, by + CH() * 0.5, S(), TXT); - let vw = 14.0 * CW(); + let vw = 18.0 * CW(); let vx = x + cols as f32 * CW() - vw - CW() * 3.0; let left = Rect::new(vx, by, CW() * 2.5, CH() * 2.0); let right = Rect::new(vx + vw - CW() * 2.5, by, CW() * 2.5, CH() * 2.0); button(gfx, left, "<", left.contains(vec2(mx, my)), FRAME); button(gfx, right, ">", right.contains(vec2(mx, my)), FRAME); let v = val.clone(); - gfx.text(&v, vx + (vw - gfx.measure(&v, S())) * 0.5, by + CH() * 0.5, S(), GOLD); + let vs = if gfx.measure(&v, S()) > vw - CW() * 5.5 { SMALL() } else { S() }; + gfx.text(&v, vx + (vw - gfx.measure(&v, vs)) * 0.5, by + CH() * 0.5, vs, GOLD); let (l, r) = match which { 0 => (Btn::MapW(-1), Btn::MapW(1)), 1 => (Btn::MapH(-1), Btn::MapH(1)), + 3 => (Btn::Land, Btn::Land), _ => (Btn::Difficulty(-1), Btn::Difficulty(1)), }; self.buttons.push((left, l)); @@ -1183,14 +1191,14 @@ impl Menu { let area = (s.map_w * s.map_h) as f32 / 4096.0; let portals = (area.round() as usize).max(1); let info = trf("newgame.info", &[portals.to_string(), (portals - 1).to_string()]); - gfx.text(&info, x + CW() * 3.0, y + CH() * 10.0, S(), DIM); + gfx.text(&info, x + CW() * 3.0, y + CH() * 12.5, S(), DIM); let opp = tr(if s.map_w * s.map_h > 128 * 128 { "newgame.opponents2" } else { "newgame.opponents1" }); let maxc = (cols - 6) as usize; for (k, l) in wrap(&opp, maxc).iter().take(2).enumerate() { - gfx.text(l, x + CW() * 3.0, y + CH() * (11.0 + k as f32), S(), DIM); + gfx.text(l, x + CW() * 3.0, y + CH() * (13.5 + k as f32), S(), DIM); } let w = (cols - 6) as f32 * CW(); - self.column(gfx, x + CW() * 3.0, y + CH() * 14.0, w, &[(Btn::Start, tr("newgame.start")), (Btn::Back, tr("menu.back"))]); + self.column(gfx, x + CW() * 3.0, y + CH() * 16.5, w, &[(Btn::Start, tr("newgame.start")), (Btn::Back, tr("menu.back"))]); } fn draw_pause(&mut self, gfx: &Gfx) { diff --git a/src/render3d.rs b/src/render3d.rs index 6faf3e3..d1e9650 100644 --- a/src/render3d.rs +++ b/src/render3d.rs @@ -449,6 +449,10 @@ fn ambient_field(map: &Map) -> Vec { (false, Owner::Hero) => 0.6, _ => 0.0, }; + // daylight above ground (cliffs and trees too) + if map.sky(i) { + f[i] = 1.6; + } if t.river() == Some(Kind::Lava) { f[i] = f[i].max(0.8); // lava lights up its surroundings } @@ -672,7 +676,11 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T for z in cz * CHUNK..((cz + 1) * CHUNK).min(mh() as i32) { for x in cx * CHUNK..((cx + 1) * CHUNK).min(mw() as i32) { let t = map.t[idx(x, z)]; - let tex = Gfx::tile_tex(t.kind, t.owner, t.var, t.reinforced); + let mut tex = Gfx::tile_tex(t.kind, t.owner, t.var, t.reinforced); + // open land above ground is grass (until a master claims it) + if map.sky(idx(x, z)) && t.kind == Kind::Floor && t.owner == Owner::None { + tex = T_GRASS + (t.var as u32 & 3); + } let (ua, ub) = cell_uv(tex); let (xf, zf) = (x as f32, z as f32); if !map.shown(idx(x, z)) { @@ -836,11 +844,13 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T c, [Vec3::Y; 4], ); - // outlines: north and west edge, only between two open tiles (each edge once) - if !solid_at(map, x, z - 1) { + // outlines: north and west edge, only between two open tiles (each edge once); + // open land above ground has none + let outdoors = map.sky(idx(x, z)) && t.owner == Owner::None; + if !solid_at(map, x, z - 1) && !outdoors { ln.strip(vec3(xf, 0.0, zf), vec3(xf + 1.0, 0.0, zf), Vec3::Y, OUTLINE_W, OUTLINE_C); } - if !solid_at(map, x - 1, z) { + 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); } // room decor @@ -871,7 +881,7 @@ fn build_chunk(map: &Map, amb: &[f32], glow: &[f32], cx: i32, cz: i32, atlas: &T } } // wear and tear: grit and cracks on plain floors - if matches!(t.kind, Kind::Floor) { + if matches!(t.kind, Kind::Floor) && !outdoors { let hsh = (x as u32).wrapping_mul(2654435761) ^ (z as u32).wrapping_mul(2246822519); let hsh = (hsh ^ (hsh >> 15)).wrapping_mul(2246822519); let decal = match hsh % 11 { @@ -1083,7 +1093,7 @@ fn chunk_sig(map: &Map, cx: i32, cz: i32) -> u64 { Kind::Drawbridge => t.res as u64, _ => 0, }; - let shown = map.shown(idx(x, z)) as u64; + let shown = map.shown(idx(x, z)) as u64 | (map.sky(idx(x, z)) as u64) << 1; (t.kind as u64) | (t.owner as u64) << 5 | (t.marked as u64) << 8 | (t.reinforced as u64) << 9 | shown << 10 | extra << 11 } None => 0xff, @@ -1462,7 +1472,9 @@ impl R3d { let amb = |x: f32, z: f32| { let (ix, iz) = (x as i32, z as i32); let own = g.map.get(ix, iz).map_or(false, |t| t.owner == Owner::Player); - if own { + if inb(ix, iz) && g.map.sky(idx(ix, iz)) { + 0.95 + } else if own { 0.75 } else { 0.5 diff --git a/src/scenes.rs b/src/scenes.rs index bc5a881..eb91397 100644 --- a/src/scenes.rs +++ b/src/scenes.rs @@ -200,4 +200,13 @@ fn export_scenes() { g.plan_walls(cx + 5, cy - 1, cx + 5, cy + 2); run(&mut g, 2.0); save("wood", &mut g, mx as f32 + 0.5, my as f32 + 0.5, 14.0, 0.0, false); + + // above ground: the cliff with a cave mouth, grass, a forest + let mut g = Game::new(23, 128, 96); + g.add_surface(23, 0.45, 3); + g.init_fog(); + let mid = (mh() / 2) as i32; + let ex = (0..mw() as i32).find(|&x| g.map.sky(idx(x, mid))).unwrap_or(60); + run(&mut g, 1.0); + save("surface", &mut g, ex as f32 + 6.0, mid as f32, 24.0, 0.0, false); } diff --git a/src/settings.rs b/src/settings.rs index 9126e3c..226eedb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -162,6 +162,7 @@ pub struct Settings { pub difficulty: u8, // rival masters in free games: 0 easy, 1 medium, 2 hard pub map_w: usize, // size of new maps (64..256) pub map_h: usize, + pub surface: bool, // new free games: the eastern half lies above ground (forest) // key bindings: action id -> key name pub keys: BTreeMap, } @@ -196,6 +197,7 @@ impl Default for Settings { difficulty: 1, map_w: 64, map_h: 64, + surface: false, keys: BTreeMap::new(), } } diff --git a/src/surface.rs b/src/surface.rs new file mode 100644 index 0000000..fad2d4e --- /dev/null +++ b/src/surface.rs @@ -0,0 +1,142 @@ +// The surface world: part of a map lies above ground - grass in daylight, forests whose trees +// imps fell for wood, winding paths, and a cliff with a few cave mouths between the dungeon in +// the rock and the open land. (The knights' fortress up there: see fortress.rs.) + +use crate::game::*; +use crate::map::*; + +// value noise in 0..1, smooth over `cell` tiles +fn noise(x: i32, y: i32, cell: i32, seed: u32) -> f32 { + let h = |x: i32, y: i32| { + let mut v = (x as u32).wrapping_mul(374761393) ^ (y as u32).wrapping_mul(668265263) ^ seed.wrapping_mul(2246822519); + v = (v ^ (v >> 13)).wrapping_mul(1274126177); + ((v ^ (v >> 16)) & 0xffff) as f32 / 65535.0 + }; + let (cx, cy) = (x.div_euclid(cell), y.div_euclid(cell)); + let (fx, fy) = ((x.rem_euclid(cell)) as f32 / cell as f32, (y.rem_euclid(cell)) as f32 / cell as f32); + let s = |t: f32| t * t * (3.0 - 2.0 * t); + let (sx, sy) = (s(fx), s(fy)); + let a = h(cx, cy) * (1.0 - sx) + h(cx + 1, cy) * sx; + let b = h(cx, cy + 1) * (1.0 - sx) + h(cx + 1, cy + 1) * sx; + a * (1.0 - sy) + b * sy +} + +impl Game { + // Everything east of `from` (share of the width, the edge a little ragged) becomes surface: + // open grass with forests and paths. A cliff along the edge keeps the dungeon apart, broken + // by `mouths` cave entrances. Mission pieces (core, portals, gate, towers ...) stay. + pub fn add_surface(&mut self, seed: u32, from: f32, mouths: u32) { + let (w, h) = (mw() as i32, mh() as i32); + let edge = |y: i32| (from * w as f32) as i32 + ((noise(0, y, 6, seed ^ 0x51) - 0.5) * 6.0) as i32; + let mut mouth_rows: Vec = (0..mouths).map(|k| ((k as f32 + 0.5) / mouths as f32 * (h - 16) as f32) as i32 + 8 + ((noise(k as i32, 3, 2, seed) - 0.5) * 8.0) as i32).collect(); + mouth_rows.sort_unstable(); + // winding paths across the land (stay open, no trees) + let paths: Vec = (0..2).map(|k| 0.25 + 0.5 * k as f32 + (noise(k, 9, 2, seed) - 0.5) * 0.2).collect(); + for y in 0..h { + let ex = edge(y); + for x in 0..w { + if x < ex - 1 { + continue; + } + let i = idx(x, y); + let t = self.map.t[i]; + let keep = matches!(t.kind, Kind::Core | Kind::Portal | Kind::Gate | Kind::Tower | Kind::Shard | Kind::ManaSpring | Kind::RuneChest | Kind::Water | Kind::Lava | Kind::Bridge | Kind::LavaBridge | Kind::Door) + || t.owner != Owner::None + || x < 2 + || y < 2 + || x >= w - 2 + || y >= h - 2; + // the cliff: two tiles of rock along the edge, open where a cave mouth is + if x <= ex { + let mouth = mouth_rows.iter().any(|&m| (y - m).abs() <= 1); + if !keep { + let tl = &mut self.map.t[i]; + if mouth { + tl.kind = Kind::Floor; + } else { + tl.kind = Kind::Rock; + } + tl.owner = Owner::None; + tl.gold = 0; + tl.marked = false; + tl.reinforced = false; + } + continue; + } + self.map.set_sky(i, true); + if keep { + continue; + } + let on_path = paths.iter().any(|&p| { + let py = p * h as f32 + (noise(x, 0, 9, seed ^ 0x77) - 0.5) * 14.0; + (y as f32 - py).abs() < 1.2 + }); + let wood = noise(x, y, 5, seed ^ 0xf0); + let rocks = noise(x, y, 4, seed ^ 0x0b); + let tl = &mut self.map.t[i]; + tl.owner = Owner::None; + tl.gold = 0; + tl.marked = false; + tl.reinforced = false; + tl.hp = 0.0; + if !on_path && rocks > 0.86 { + tl.kind = Kind::Rock; + } else if !on_path && wood > 0.52 && (x * 7 + y * 13) % 3 != 0 { + tl.kind = Kind::Tree; + tl.hp = crate::wood::TREE_HP; + } else { + tl.kind = Kind::Floor; + } + } + } + // the cave mouths run through into the dungeon: dig until open ground is reached + for &m in &mouth_rows { + let ex = edge(m); + let mut x = ex - 2; + while x > 2 { + let i = idx(x, m); + if self.map.t[i].walkable() { + break; + } + if self.map.t[i].solid() && !matches!(self.map.t[i].kind, Kind::Steel) { + let tl = &mut self.map.t[i]; + tl.kind = Kind::Floor; + tl.owner = Owner::None; + tl.gold = 0; + } + x -= 1; + } + } + self.recount(); + } + + // the share of the map above ground (0: a dungeon map) + #[allow(dead_code)] // tests and debugging + pub fn surface_share(&self) -> f32 { + if self.map.sky.is_empty() { + return 0.0; + } + self.map.sky.iter().filter(|&&s| s).count() as f32 / self.map.sky.len() as f32 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn half_the_map_becomes_a_forest_above_ground() { + let mut g = Game::new(21, 128, 96); + let core = g.map.core; + g.add_surface(21, 0.5, 3); + let share = g.surface_share(); + assert!(share > 0.35 && share < 0.6, "share {}", share); + let trees = g.map.t.iter().filter(|t| t.kind == Kind::Tree).count(); + assert!(trees > 200, "{} trees", trees); + assert_eq!(g.map.core, core); + assert_eq!(g.map.t[idx(core.0 + 1, core.1 + 1)].kind, Kind::Core); + // the surface can be reached from the core (cave mouths, trees can be felled) + let goal = |i: usize| g.map.sky(i) && g.map.t[i].walkable(); + assert!(g.map.search(idx(core.0 + 1, core.1 + 3), g.map.hero_cost_as(false), goal, true).is_some()); + } +}