1use super::{Rng, Grid2D};
8use super::biomes::{BiomeMap, Biome};
9use super::rivers::RiverNetwork;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum SettlementSize {
14 Hamlet, Village, Town, City, Capital, }
20
21impl SettlementSize {
22 pub fn building_range(self) -> (usize, usize) {
23 match self {
24 Self::Hamlet => (5, 20),
25 Self::Village => (20, 80),
26 Self::Town => (80, 300),
27 Self::City => (300, 1000),
28 Self::Capital => (1000, 3000),
29 }
30 }
31
32 pub fn population_range(self) -> (usize, usize) {
33 match self {
34 Self::Hamlet => (10, 100),
35 Self::Village => (100, 500),
36 Self::Town => (500, 5000),
37 Self::City => (5000, 50000),
38 Self::Capital => (50000, 500000),
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
45pub enum BuildingType {
46 House,
47 Farm,
48 Market,
49 Temple,
50 Barracks,
51 Wall,
52 Gate,
53 Tavern,
54 Smithy,
55 Library,
56 Palace,
57 Port,
58 Mine,
59 Warehouse,
60 Workshop,
61}
62
63#[derive(Debug, Clone)]
65pub struct Building {
66 pub building_type: BuildingType,
67 pub x: f32,
68 pub y: f32,
69 pub width: f32,
70 pub height: f32,
71 pub stories: u8,
72}
73
74#[derive(Debug, Clone)]
76pub struct Road {
77 pub start: (f32, f32),
78 pub end: (f32, f32),
79 pub width: f32,
80 pub is_main: bool,
81}
82
83#[derive(Debug, Clone)]
85pub struct Settlement {
86 pub id: u32,
87 pub name: String,
88 pub grid_x: usize,
89 pub grid_y: usize,
90 pub size: SettlementSize,
91 pub population: usize,
92 pub buildings: Vec<Building>,
93 pub roads: Vec<Road>,
94 pub biome: Biome,
95 pub near_river: bool,
96 pub near_coast: bool,
97 pub owner_civ: Option<u32>,
99 pub founded_year: i32,
101 pub resources: f32,
103 pub defense: f32,
105}
106
107pub fn place(
109 heightmap: &Grid2D,
110 biome_map: &BiomeMap,
111 rivers: &RiverNetwork,
112 max_settlements: usize,
113 rng: &mut Rng,
114) -> Vec<Settlement> {
115 let w = heightmap.width;
116 let h = heightmap.height;
117
118 let mut scores: Vec<(usize, usize, f32)> = Vec::new();
120 for y in 2..h - 2 {
121 for x in 2..w - 2 {
122 let biome = biome_map.biome_at(x, y);
123 if biome.is_water() { continue; }
124
125 let mut score = biome.habitability();
126
127 if rivers.is_river(x, y) || has_river_neighbor(rivers, x, y, w, h) {
129 score += 0.3;
130 }
131
132 let (gx, gy) = heightmap.gradient(x, y);
134 let slope = (gx * gx + gy * gy).sqrt();
135 score += (1.0 - slope * 5.0).max(0.0) * 0.2;
136
137 score += biome.resources() * 0.2;
139
140 if score > 0.3 {
141 scores.push((x, y, score));
142 }
143 }
144 }
145
146 scores.sort_by(|a, b| b.2.partial_cmp(&a.2).unwrap());
148
149 let min_dist_sq = (w / 8).max(4).pow(2);
151 let mut placed: Vec<(usize, usize)> = Vec::new();
152 let mut settlements = Vec::new();
153 let mut next_id = 0u32;
154
155 for &(x, y, score) in &scores {
156 if settlements.len() >= max_settlements { break; }
157
158 let too_close = placed.iter().any(|&(px, py)| {
160 let dx = x as i32 - px as i32;
161 let dy = y as i32 - py as i32;
162 (dx * dx + dy * dy) < min_dist_sq as i32
163 });
164 if too_close { continue; }
165
166 let size = if score > 0.85 {
167 SettlementSize::City
168 } else if score > 0.7 {
169 SettlementSize::Town
170 } else if score > 0.5 {
171 SettlementSize::Village
172 } else {
173 SettlementSize::Hamlet
174 };
175
176 let pop_range = size.population_range();
177 let population = rng.range_usize(pop_range.0, pop_range.1);
178
179 let biome = biome_map.biome_at(x, y);
180 let near_river = rivers.is_river(x, y) || has_river_neighbor(rivers, x, y, w, h);
181
182 let (buildings, roads) = generate_layout(size, rng);
183
184 settlements.push(Settlement {
185 id: next_id,
186 name: generate_name(rng),
187 grid_x: x,
188 grid_y: y,
189 size,
190 population,
191 buildings,
192 roads,
193 biome,
194 near_river,
195 near_coast: false, owner_civ: None,
197 founded_year: 0,
198 resources: biome.resources(),
199 defense: 0.1,
200 });
201
202 placed.push((x, y));
203 next_id += 1;
204 }
205
206 settlements
207}
208
209fn has_river_neighbor(rivers: &RiverNetwork, x: usize, y: usize, w: usize, h: usize) -> bool {
210 for &(dx, dy) in &[(-1i32, 0), (1, 0), (0, -1), (0, 1)] {
211 let nx = (x as i32 + dx).clamp(0, w as i32 - 1) as usize;
212 let ny = (y as i32 + dy).clamp(0, h as i32 - 1) as usize;
213 if rivers.is_river(nx, ny) { return true; }
214 }
215 false
216}
217
218fn generate_layout(size: SettlementSize, rng: &mut Rng) -> (Vec<Building>, Vec<Road>) {
220 let (min_b, max_b) = size.building_range();
221 let num_buildings = rng.range_usize(min_b, max_b);
222
223 let mut buildings = Vec::with_capacity(num_buildings);
224 let mut roads = Vec::new();
225
226 let main_len = num_buildings as f32 * 0.3;
228 roads.push(Road {
229 start: (-main_len * 0.5, 0.0),
230 end: (main_len * 0.5, 0.0),
231 width: 2.0,
232 is_main: true,
233 });
234
235 let num_cross = (num_buildings / 20).max(1);
237 for i in 0..num_cross {
238 let t = (i as f32 + 0.5) / num_cross as f32;
239 let cx = -main_len * 0.5 + main_len * t;
240 let branch_len = main_len * rng.range_f32(0.2, 0.5);
241 roads.push(Road {
242 start: (cx, 0.0),
243 end: (cx, branch_len),
244 width: 1.5,
245 is_main: false,
246 });
247 roads.push(Road {
248 start: (cx, 0.0),
249 end: (cx, -branch_len),
250 width: 1.5,
251 is_main: false,
252 });
253 }
254
255 for i in 0..num_buildings {
257 let road_idx = i % roads.len();
258 let road = &roads[road_idx];
259 let t = rng.next_f32();
260 let rx = road.start.0 + (road.end.0 - road.start.0) * t;
261 let ry = road.start.1 + (road.end.1 - road.start.1) * t;
262 let offset = if rng.coin(0.5) { road.width + 1.0 } else { -(road.width + 1.0) };
263
264 let btype = pick_building_type(i, num_buildings, rng);
265 let (bw, bh) = building_size(btype);
266
267 buildings.push(Building {
268 building_type: btype,
269 x: rx + if road.start.0 == road.end.0 { offset } else { 0.0 },
270 y: ry + if road.start.1 == road.end.1 { offset } else { 0.0 },
271 width: bw,
272 height: bh,
273 stories: if matches!(btype, BuildingType::Palace | BuildingType::Temple | BuildingType::Library) { 3 } else { rng.range_u32(1, 3) as u8 },
274 });
275 }
276
277 (buildings, roads)
278}
279
280fn pick_building_type(index: usize, total: usize, rng: &mut Rng) -> BuildingType {
281 if index == 0 { return BuildingType::Market; }
282 if index == 1 { return BuildingType::Temple; }
283 if index == 2 && total > 50 { return BuildingType::Palace; }
284 match rng.range_u32(0, 10) {
285 0..=4 => BuildingType::House,
286 5 => BuildingType::Farm,
287 6 => BuildingType::Tavern,
288 7 => BuildingType::Smithy,
289 8 => BuildingType::Workshop,
290 _ => BuildingType::Warehouse,
291 }
292}
293
294fn building_size(btype: BuildingType) -> (f32, f32) {
295 match btype {
296 BuildingType::House => (2.0, 2.0),
297 BuildingType::Farm => (4.0, 3.0),
298 BuildingType::Market => (5.0, 5.0),
299 BuildingType::Temple => (4.0, 6.0),
300 BuildingType::Palace => (8.0, 8.0),
301 BuildingType::Barracks => (5.0, 4.0),
302 BuildingType::Tavern => (3.0, 3.0),
303 BuildingType::Smithy => (3.0, 2.5),
304 BuildingType::Library => (4.0, 4.0),
305 BuildingType::Port => (6.0, 3.0),
306 BuildingType::Mine => (3.0, 3.0),
307 BuildingType::Warehouse => (4.0, 3.0),
308 BuildingType::Workshop => (3.0, 3.0),
309 BuildingType::Wall => (1.0, 1.0),
310 BuildingType::Gate => (2.0, 2.0),
311 }
312}
313
314fn generate_name(rng: &mut Rng) -> String {
316 let prefixes = ["Ash", "Oak", "Iron", "Storm", "Frost", "Shadow", "Gold", "Silver",
317 "Red", "Blue", "Green", "White", "Black", "Stone", "River", "Lake",
318 "Moon", "Sun", "Star", "Wind", "Fire", "Ice", "Dark", "Light"];
319 let suffixes = ["ford", "dale", "holm", "bury", "bridge", "gate", "keep",
320 "haven", "port", "vale", "fell", "crest", "wood", "field", "ton",
321 "wick", "march", "mire", "shore", "cliff"];
322
323 let prefix = prefixes[rng.next_u64() as usize % prefixes.len()];
324 let suffix = suffixes[rng.next_u64() as usize % suffixes.len()];
325 format!("{}{}", prefix, suffix)
326}
327
328#[cfg(test)]
329mod tests {
330 use super::*;
331
332 #[test]
333 fn test_settlement_name() {
334 let mut rng = Rng::new(42);
335 let name = generate_name(&mut rng);
336 assert!(!name.is_empty());
337 }
338
339 #[test]
340 fn test_layout_generation() {
341 let mut rng = Rng::new(42);
342 let (buildings, roads) = generate_layout(SettlementSize::Village, &mut rng);
343 assert!(!buildings.is_empty());
344 assert!(!roads.is_empty());
345 }
346}