1use super::SpawnRules;
2use crate::{
3 IndexRef,
4 column::ColumnSample,
5 sim::WorldSim,
6 site::{
7 namegen::NameGen,
8 settlement::building::{
9 Archetype, Ori,
10 archetype::keep::{Attr, FlagColor, Keep as KeepArchetype, StoneColor},
11 },
12 },
13};
14use common::{
15 generation::ChunkSupplement,
16 terrain::{Block, BlockKind, SpriteKind},
17 vol::{ReadVol, RectSizedVol, WriteVol},
18};
19use core::f32;
20use rand::prelude::*;
21use serde::Deserialize;
22use vek::*;
23
24struct Keep {
25 offset: Vec2<i32>,
26 locus: i32,
27 storeys: i32,
28 is_tower: bool,
29 alt: i32,
30}
31
32struct Tower {
33 offset: Vec2<i32>,
34 alt: i32,
35}
36
37pub struct Castle {
38 name: String,
39 origin: Vec2<i32>,
40 radius: i32,
42 towers: Vec<Tower>,
43 keeps: Vec<Keep>,
44 rounded_towers: bool,
45 ridged: bool,
46 flags: bool,
47
48 evil: bool,
49}
50
51pub struct GenCtx<'a, R: Rng> {
52 sim: Option<&'a mut WorldSim>,
53 rng: &'a mut R,
54}
55
56#[derive(Deserialize)]
57pub struct Colors;
58
59impl Castle {
60 pub fn generate(wpos: Vec2<i32>, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self {
61 let ctx = GenCtx { sim, rng };
62
63 let boundary_towers = ctx.rng.gen_range(5..10);
64 let keep_count = ctx.rng.gen_range(1..4);
65 let boundary_noise = ctx.rng.gen_range(-2i32..8).max(1) as f32;
66
67 let radius = 150;
68
69 let this = Self {
70 name: {
71 let name = NameGen::location(ctx.rng).generate();
72 match ctx.rng.gen_range(0..6) {
73 0 => format!("Fort {}", name),
74 1 => format!("{} Citadel", name),
75 2 => format!("{} Castle", name),
76 3 => format!("{} Stronghold", name),
77 4 => format!("{} Fortress", name),
78 _ => format!("{} Keep", name),
79 }
80 },
81 origin: wpos,
82 radius,
90
91 towers: (0..boundary_towers)
92 .map(|i| {
93 let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
94 let dir = Vec2::new(angle.cos(), angle.sin());
95 let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0;
96
97 let mut offset = (dir * dist).map(|e| e as i32);
98 for i in (1..80).step_by(5) {
100 if ctx
101 .sim
102 .as_ref()
103 .and_then(|sim| sim.get_nearest_path(wpos + offset))
104 .map(|(dist, _, _, _)| dist > 24.0)
105 .unwrap_or(true)
106 {
107 break;
108 }
109 offset = (dir * dist)
110 .map(|e| (e + ctx.rng.gen_range(-1.0..1.0) * i as f32) as i32);
111 }
112
113 Tower {
114 offset,
115 alt: ctx
116 .sim
117 .as_ref()
118 .and_then(|sim| sim.get_alt_approx(wpos + offset))
119 .unwrap_or(0.0) as i32
120 + 2,
121 }
122 })
123 .collect(),
124 rounded_towers: ctx.rng.gen(),
125 ridged: ctx.rng.gen(),
126 flags: ctx.rng.gen(),
127 evil: ctx.rng.gen(),
128 keeps: (0..keep_count)
129 .map(|i| {
130 let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0;
131 let dir = Vec2::new(angle.cos(), angle.sin());
132 let dist =
133 (radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3;
134
135 let locus = ctx.rng.gen_range(20..26);
136 let offset = (dir * dist).map(|e| e as i32);
137 let storeys = ctx.rng.gen_range(1..8).clamped(3, 5);
138
139 Keep {
140 offset,
141 locus,
142 storeys,
143 is_tower: true,
144 alt: ctx
145 .sim
146 .as_ref()
147 .and_then(|sim| sim.get_alt_approx(wpos + offset))
148 .unwrap_or(0.0) as i32
149 + 2,
150 }
151 })
152 .collect(),
153 };
154
155 this
156 }
157
158 pub fn name(&self) -> &str { &self.name }
159
160 pub fn contains_point(&self, wpos: Vec2<i32>) -> bool {
161 let lpos = wpos - self.origin;
162 for i in 0..self.towers.len() {
163 let tower0 = &self.towers[i];
164 let tower1 = &self.towers[(i + 1) % self.towers.len()];
165
166 if lpos.determine_side(Vec2::zero(), tower0.offset) > 0
167 && lpos.determine_side(Vec2::zero(), tower1.offset) <= 0
168 && lpos.determine_side(tower0.offset, tower1.offset) > 0
169 {
170 return true;
171 }
172 }
173
174 false
175 }
176
177 pub fn get_origin(&self) -> Vec2<i32> { self.origin }
178
179 pub fn radius(&self) -> f32 { 200.0 }
180
181 pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
182 SpawnRules {
183 trees: wpos.distance_squared(self.origin) > self.radius.pow(2),
184 ..SpawnRules::default()
185 }
186 }
187
188 pub fn apply_to<'a>(
189 &'a self,
190 index: IndexRef,
191 wpos2d: Vec2<i32>,
192 mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
193 vol: &mut (impl RectSizedVol<Vox = Block> + ReadVol + WriteVol),
194 ) {
195 for y in 0..vol.size_xy().y as i32 {
196 for x in 0..vol.size_xy().x as i32 {
197 let offs = Vec2::new(x, y);
198
199 let wpos2d = wpos2d + offs;
200 let rpos = wpos2d - self.origin;
201
202 if rpos.magnitude_squared() > (self.radius + 64).pow(2) {
203 continue;
204 }
205
206 let col_sample = if let Some(col) = get_column(offs) {
207 col
208 } else {
209 continue;
210 };
211
212 if self.contains_point(wpos2d) {
214 let surface_z = col_sample.alt as i32;
215 for z in -5..3 {
216 let pos = Vec3::new(offs.x, offs.y, surface_z + z);
217
218 if z > 0 {
219 if vol.get(pos).unwrap().kind() != BlockKind::Water {
220 let _ = vol.set(pos, Block::air(SpriteKind::Empty));
222 }
223 } else {
224 let _ = vol.set(
225 pos,
226 Block::new(
227 BlockKind::Earth,
228 col_sample.sub_surface_color.map(|e| (e * 255.0) as u8),
229 ),
230 );
231 }
232 }
233 }
234
235 let (wall_dist, wall_pos, wall_alt, wall_ori, _towers) = (0..self.towers.len())
236 .map(|i| {
237 let tower0 = &self.towers[i];
238 let tower1 = &self.towers[(i + 1) % self.towers.len()];
239
240 let wall = LineSegment2 {
241 start: tower0.offset.map(|e| e as f32),
242 end: tower1.offset.map(|e| e as f32),
243 };
244
245 let projected = wall
246 .projected_point(rpos.map(|e| e as f32))
247 .map(|e| e.floor() as i32);
248
249 let tower0_dist = tower0
250 .offset
251 .map(|e| e as f32)
252 .distance(projected.map(|e| e as f32));
253 let tower1_dist = tower1
254 .offset
255 .map(|e| e as f32)
256 .distance(projected.map(|e| e as f32));
257 let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
258 let wall_ori = if (tower0.offset.x - tower1.offset.x).abs()
259 < (tower0.offset.y - tower1.offset.y).abs()
260 {
261 Ori::North
262 } else {
263 Ori::East
264 };
265
266 (
267 wall.distance_to_point(rpos.map(|e| e as f32)) as i32,
268 projected,
269 Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32,
270 wall_ori,
271 [tower0, tower1],
272 )
273 })
274 .min_by_key(|x| x.0)
275 .unwrap();
276 let border_pos = (wall_pos - rpos).map(|e| e.abs());
277 let wall_rpos = if wall_ori == Ori::East {
278 rpos
279 } else {
280 rpos.yx()
281 };
282 let head_space = col_sample
283 .path
284 .map(|(dist, _, path, _)| path.head_space(dist))
285 .unwrap_or(0);
286
287 let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) {
288 col
289 } else {
290 col_sample
291 };
292
293 let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0);
295
296 let keep_archetype = KeepArchetype {
297 flag_color: if self.evil {
298 FlagColor::Evil
299 } else {
300 FlagColor::Good
301 },
302 stone_color: if self.evil {
303 StoneColor::Evil
304 } else {
305 StoneColor::Good
306 },
307 };
308
309 for z in -10..64 {
310 let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
311
312 let wall_z = wpos.z - wall_alt;
314 if z < head_space {
315 continue;
316 }
317
318 let mut mask = keep_archetype.draw(
319 index,
320 Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
321 wall_dist,
322 border_pos,
323 rpos - wall_pos,
324 wall_z,
325 wall_ori,
326 4,
327 0,
328 &Attr {
329 storeys: 2,
330 is_tower: false,
331 flag: self.flags,
332 ridged: false,
333 rounded: true,
334 has_doors: false,
335 },
336 );
337
338 for tower in &self.towers {
340 let tower_wpos = Vec3::new(
341 self.origin.x + tower.offset.x,
342 self.origin.y + tower.offset.y,
343 tower.alt,
344 );
345 let tower_locus = 10;
346
347 let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
348 mask = mask.resolve_with(keep_archetype.draw(
349 index,
350 if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
351 wpos - tower_wpos
352 } else {
353 Vec3::new(
354 wpos.y - tower_wpos.y,
355 wpos.x - tower_wpos.x,
356 wpos.z - tower_wpos.z,
357 )
358 },
359 border_pos.reduce_max() - tower_locus,
360 Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
361 (wpos - tower_wpos).xy(),
362 wpos.z - tower.alt,
363 if border_pos.x > border_pos.y {
364 Ori::East
365 } else {
366 Ori::North
367 },
368 tower_locus,
369 0,
370 &Attr {
371 storeys: 3,
372 is_tower: true,
373 flag: self.flags,
374 ridged: self.ridged,
375 rounded: self.rounded_towers,
376 has_doors: false,
377 },
378 ));
379 }
380
381 for keep in &self.keeps {
383 let keep_wpos = Vec3::new(
384 self.origin.x + keep.offset.x,
385 self.origin.y + keep.offset.y,
386 keep.alt,
387 );
388
389 let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs());
390 mask = mask.resolve_with(keep_archetype.draw(
391 index,
392 if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() {
393 wpos - keep_wpos
394 } else {
395 Vec3::new(
396 wpos.y - keep_wpos.y,
397 wpos.x - keep_wpos.x,
398 wpos.z - keep_wpos.z,
399 )
400 },
401 border_pos.reduce_max() - keep.locus,
402 Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()),
403 (wpos - keep_wpos).xy(),
404 wpos.z - keep.alt,
405 if border_pos.x > border_pos.y {
406 Ori::East
407 } else {
408 Ori::North
409 },
410 keep.locus,
411 0,
412 &Attr {
413 storeys: keep.storeys,
414 is_tower: keep.is_tower,
415 flag: self.flags,
416 ridged: self.ridged,
417 rounded: self.rounded_towers,
418 has_doors: true,
419 },
420 ));
421 }
422
423 if let Some(block) = mask.finish() {
424 let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block);
425 }
426 }
427 }
428 }
429 }
430
431 pub fn apply_supplement<'a>(
432 &'a self,
433 _dynamic_rng: &mut impl Rng,
435 _wpos2d: Vec2<i32>,
436 _get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
437 _supplement: &mut ChunkSupplement,
438 ) {
439 }
441}