veloren_world/site/plot/cliff_tower.rs
1use super::*;
2use crate::{
3 Land,
4 site::util::{gradient::WrapMode, sprites::PainterSpriteExt},
5 util::{DIAGONALS, LOCALITY, RandomField, Sampler},
6};
7use common::{
8 generation::{EntityInfo, SpecialEntity},
9 terrain::{BlockKind, SpriteKind},
10};
11use rand::prelude::*;
12use std::{f32::consts::TAU, mem};
13use vek::*;
14
15/// Represents house data generated by the `generate()` method
16pub struct CliffTower {
17 /// Tile position of the door tile
18 pub door_tile: Vec2<i32>,
19 /// Axis aligned bounding region for the house
20 bounds: Aabr<i32>,
21 /// Approximate altitude of the door tile
22 pub(crate) alt: i32,
23 campfire: bool,
24 door_dir: Vec2<i32>,
25 surface_color: Rgb<f32>,
26 sub_surface_color: Rgb<f32>,
27}
28
29impl CliffTower {
30 pub fn generate(
31 land: &Land,
32 index: IndexRef,
33 _rng: &mut impl Rng,
34 site: &Site,
35 door_tile: Vec2<i32>,
36 door_dir: Vec2<i32>,
37 tile_aabr: Aabr<i32>,
38 campfire: bool,
39 alt: Option<i32>,
40 ) -> Self {
41 let door_tile_pos = site.tile_center_wpos(door_tile);
42 let bounds = Aabr {
43 min: site.tile_wpos(tile_aabr.min),
44 max: site.tile_wpos(tile_aabr.max),
45 };
46 let (surface_color, sub_surface_color) =
47 if let Some(sample) = land.column_sample(bounds.center(), index) {
48 (sample.surface_color, sample.sub_surface_color)
49 } else {
50 (Rgb::new(161.0, 116.0, 86.0), Rgb::new(88.0, 64.0, 64.0))
51 };
52 Self {
53 door_tile: door_tile_pos,
54 bounds,
55 alt: alt.unwrap_or_else(|| land.get_alt_approx(door_tile_pos) as i32),
56 campfire,
57 door_dir,
58 surface_color,
59 sub_surface_color,
60 }
61 }
62}
63
64impl Structure for CliffTower {
65 #[cfg(feature = "dyn-lib")]
66 #[unsafe(export_name = "as_dyn_structure_clifftower")]
67 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
68 Some((Self::as_dyn_impl(self), "as_dyn_structure_clifftower"))
69 }
70
71 fn render_inner(&self, _site: &Site, _land: &Land, painter: &Painter) {
72 let base = self.alt;
73 let plot_center = self.bounds.center();
74 let door_dir = self.door_dir;
75
76 let surface_color = self.surface_color.map(|e| (e * 255.0) as u8);
77 let sub_surface_color = self.sub_surface_color.map(|e| (e * 255.0) as u8);
78 let gradient_center = Vec3::new(
79 plot_center.x as f32,
80 plot_center.y as f32,
81 (base + 1) as f32,
82 );
83 let gradient_var_1 = RandomField::new(0).get(plot_center.with_z(base)) as i32 % 8;
84 let gradient_var_2 = RandomField::new(0).get(plot_center.with_z(base + 1)) as i32 % 10;
85
86 let brick = Fill::Gradient(
87 util::gradient::Gradient::new(
88 gradient_center,
89 8.0 + gradient_var_1 as f32,
90 util::gradient::Shape::Point,
91 (surface_color, sub_surface_color),
92 )
93 .with_repeat(if gradient_var_2 > 5 {
94 WrapMode::Repeat
95 } else {
96 WrapMode::PingPong
97 }),
98 BlockKind::Rock,
99 );
100
101 let wood = Fill::Brick(BlockKind::Wood, Rgb::new(106, 83, 51), 12);
102 let color = Fill::Block(Block::air(SpriteKind::CliffDecorBlock));
103 let window = Fill::Block(Block::air(SpriteKind::WindowArabic));
104 let window2 = Fill::Block(Block::air(SpriteKind::WindowArabic).with_ori(2).unwrap());
105 let rope = Fill::Block(Block::air(SpriteKind::Rope));
106
107 let tube_var = RandomField::new(0).get(plot_center.with_z(base)) as i32 % 6;
108 let radius = 10.0 + tube_var as f32;
109 let tubes = 3.0 + tube_var as f32;
110 let phi = TAU / tubes;
111 for n in 1..=tubes as i32 {
112 let center = Vec2::new(
113 plot_center.x + (radius * ((n as f32 * phi).cos())) as i32,
114 plot_center.y + (radius * ((n as f32 * phi).sin())) as i32,
115 );
116
117 let variant_pos = center.with_z(base);
118 let variant = RandomField::new(0).get(variant_pos) as i32 % 10;
119 // common superquadric degree for rooms
120 let sq_type = 3.5;
121 let storeys = 5 + (variant / 2);
122 let mut length = 16 + (variant / 2);
123 let mut width = 7 * length / 8;
124 let mut height = 18 + variant / 2;
125 let mut floor_level = base - 40;
126 let mut workshops = 0;
127 let mut ground_entries = 0;
128 for s in 0..storeys {
129 let x_offset = RandomField::new(0).get((center - length).with_z(base)) as i32 % 10;
130 let y_offset = RandomField::new(0).get((center + length).with_z(base)) as i32 % 10;
131 let super_center =
132 Vec2::new(center.x - 3 + x_offset / 2, center.y - 3 + y_offset / 2);
133 // CliffTower Hoodoo Overlay
134 painter
135 .cubic_bezier(
136 super_center.with_z(floor_level + (height / 2)),
137 (super_center - x_offset).with_z(floor_level + height),
138 (super_center - y_offset).with_z(floor_level + (height) + (height / 2)),
139 super_center.with_z(floor_level + (2 * height)),
140 (length - 1) as f32,
141 )
142 .fill(brick.clone());
143 if s == (storeys - 1) {
144 for dir in LOCALITY {
145 let cone_pos = super_center + (dir * 2);
146 let cone_var =
147 4 + RandomField::new(0).get(cone_pos.with_z(base)) as i32 % 4;
148 painter
149 .cone_with_radius(
150 cone_pos.with_z(floor_level + (2 * height) + 5),
151 (length / 2) as f32,
152 (length + cone_var) as f32,
153 )
154 .fill(brick.clone());
155 }
156 }
157 // center tube with rooms
158 if n == tubes as i32 {
159 // ground_entries
160 if ground_entries < 1 && floor_level > (base - 6) {
161 for dir in CARDINALS {
162 let entry_pos_inner = plot_center + (dir * (2 * length) - 4);
163 let entry_pos_outer = plot_center + (dir * (3 * length) + 4);
164 painter
165 .line(
166 entry_pos_inner.with_z(floor_level + 6),
167 entry_pos_outer.with_z(base + 35),
168 6.0,
169 )
170 .clear();
171 }
172 let door_start = plot_center + door_dir * ((3 * (length / 2)) + 1);
173 painter
174 .line(
175 door_start.with_z(floor_level + 2),
176 self.door_tile.with_z(base),
177 4.0,
178 )
179 .fill(wood.clone());
180 painter
181 .line(
182 door_start.with_z(floor_level + 7),
183 self.door_tile.with_z(base + 6),
184 7.0,
185 )
186 .clear();
187 ground_entries += 1;
188 }
189 painter
190 .cubic_bezier(
191 plot_center.with_z(floor_level + (height / 2)),
192 (plot_center - x_offset).with_z(floor_level + height),
193 (plot_center - y_offset).with_z(floor_level + (height) + (height / 2)),
194 plot_center.with_z(floor_level + (2 * height)),
195 (length + 2) as f32,
196 )
197 .fill(brick.clone());
198 // platforms on some upper storeys
199 let balcony = RandomField::new(0).get((plot_center - floor_level).with_z(base))
200 as i32
201 % 2;
202 if storeys > 3 && floor_level > base + 25 && balcony == 0 {
203 let limit_up = painter.aabb(Aabb {
204 min: (plot_center - (2 * length) - 2).with_z(floor_level - 4),
205 max: (plot_center + (2 * length) + 2).with_z(floor_level + 1),
206 });
207 painter
208 .superquadric(
209 Aabb {
210 min: (plot_center - (2 * length) - 2).with_z(floor_level - 4),
211 max: (plot_center + (2 * length) + 2).with_z(floor_level + 6),
212 },
213 4.0,
214 )
215 .intersect(limit_up)
216 .fill(wood.clone());
217 // lanterns & random sprites for wood platform corners
218 for dir in DIAGONALS {
219 let sprite_pos = plot_center + (dir * ((2 * length) - 4));
220 painter
221 .aabb(Aabb {
222 min: sprite_pos.with_z(floor_level + 1),
223 max: (sprite_pos + 1).with_z(floor_level + 2),
224 })
225 .clear();
226 painter.owned_resource_sprite(
227 sprite_pos.with_z(floor_level + 1),
228 match (RandomField::new(0).get(sprite_pos.with_z(floor_level + 1)))
229 % 8
230 {
231 0 => SpriteKind::Bowl,
232 1 => SpriteKind::VialEmpty,
233 2 => SpriteKind::Crate,
234 3 => SpriteKind::FlowerpotWoodWoodlandS,
235 _ => SpriteKind::MesaLantern,
236 },
237 0,
238 );
239 }
240 // planters
241 for r in 0..2 {
242 for p in 0..((length / 2) - 2) {
243 let planter_pos_1 = Vec2::new(
244 plot_center.x - (2 * (length / 3)) + (p * (length / 3)),
245 plot_center.y - ((2 * length) + 1) + (r * ((4 * length) + 1)),
246 );
247 painter
248 .aabb(Aabb {
249 min: Vec2::new(planter_pos_1.x - 1, planter_pos_1.y)
250 .with_z(floor_level + 1),
251 max: Vec2::new(planter_pos_1.x + 2, planter_pos_1.y + 1)
252 .with_z(floor_level + 3),
253 })
254 .clear();
255 painter.rotated_sprite(
256 planter_pos_1.with_z(floor_level + 1),
257 SpriteKind::Planter,
258 (4 - (r * 4)) as u8,
259 );
260 let planter_pos_2 = Vec2::new(
261 plot_center.x - ((2 * length) + 1) + (r * ((4 * length) + 1)),
262 plot_center.y - (2 * (length / 3)) + (p * (length / 3)),
263 );
264 painter
265 .aabb(Aabb {
266 min: Vec2::new(planter_pos_2.x, planter_pos_2.y - 1)
267 .with_z(floor_level + 1),
268 max: Vec2::new(planter_pos_2.x + 1, planter_pos_2.y + 2)
269 .with_z(floor_level + 3),
270 })
271 .clear();
272 painter.rotated_sprite(
273 planter_pos_2.with_z(floor_level + 1),
274 SpriteKind::Planter,
275 (6 - (r * 4)) as u8,
276 );
277 }
278 }
279 }
280
281 // clear rooms and entries & decor
282 if floor_level > (base - 6) {
283 // decor
284 painter
285 .line(
286 Vec2::new(plot_center.x, plot_center.y - length)
287 .with_z(floor_level + 5),
288 Vec2::new(plot_center.x, plot_center.y + length)
289 .with_z(floor_level + 5),
290 4.0,
291 )
292 .fill(color.clone());
293 painter
294 .line(
295 Vec2::new(plot_center.x - length, plot_center.y)
296 .with_z(floor_level + 5),
297 Vec2::new(plot_center.x + length, plot_center.y)
298 .with_z(floor_level + 5),
299 4.0,
300 )
301 .fill(color.clone());
302 // entries
303 painter
304 .line(
305 Vec2::new(plot_center.x, plot_center.y - (2 * length) - 4)
306 .with_z(floor_level + 4),
307 Vec2::new(plot_center.x, plot_center.y + (2 * length) + 4)
308 .with_z(floor_level + 4),
309 4.0,
310 )
311 .clear();
312 painter
313 .line(
314 Vec2::new(plot_center.x - (2 * length) - 4, plot_center.y)
315 .with_z(floor_level + 4),
316 Vec2::new(plot_center.x + (2 * length) + 4, plot_center.y)
317 .with_z(floor_level + 4),
318 4.0,
319 )
320 .clear();
321 painter
322 .superquadric(
323 Aabb {
324 min: (plot_center - length - 1).with_z(floor_level),
325 max: (plot_center + length + 1)
326 .with_z(floor_level + height - 4),
327 },
328 sq_type,
329 )
330 .clear();
331 // room floor
332 painter
333 .cylinder(Aabb {
334 min: (plot_center - length - 3).with_z(floor_level),
335 max: (plot_center + length + 3).with_z(floor_level + 1),
336 })
337 .fill(brick.clone());
338 painter
339 .cylinder(Aabb {
340 min: (plot_center - length + 1).with_z(floor_level),
341 max: (plot_center + length - 1).with_z(floor_level + 1),
342 })
343 .fill(color.clone());
344 painter
345 .cylinder(Aabb {
346 min: (plot_center - length + 2).with_z(floor_level),
347 max: (plot_center + length - 2).with_z(floor_level + 1),
348 })
349 .fill(brick.clone());
350 // entry sprites
351 painter
352 .aabb(Aabb {
353 min: Vec2::new(plot_center.x - 3, plot_center.y + length)
354 .with_z(floor_level + 2),
355 max: Vec2::new(plot_center.x + 4, plot_center.y + length + 1)
356 .with_z(floor_level + 7),
357 })
358 .fill(window2.clone());
359 painter
360 .aabb(Aabb {
361 min: Vec2::new(plot_center.x - 2, plot_center.y + length)
362 .with_z(floor_level + 2),
363 max: Vec2::new(plot_center.x + 3, plot_center.y + length + 1)
364 .with_z(floor_level + 7),
365 })
366 .clear();
367
368 painter
369 .aabb(Aabb {
370 min: Vec2::new(plot_center.x - 3, plot_center.y - length - 1)
371 .with_z(floor_level + 2),
372 max: Vec2::new(plot_center.x + 4, plot_center.y - length)
373 .with_z(floor_level + 7),
374 })
375 .fill(window2.clone());
376 painter
377 .aabb(Aabb {
378 min: Vec2::new(plot_center.x - 2, plot_center.y - length - 1)
379 .with_z(floor_level + 2),
380 max: Vec2::new(plot_center.x + 3, plot_center.y - length)
381 .with_z(floor_level + 7),
382 })
383 .clear();
384 painter
385 .aabb(Aabb {
386 min: Vec2::new(plot_center.x + length, plot_center.y - 3)
387 .with_z(floor_level + 2),
388 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 4)
389 .with_z(floor_level + 7),
390 })
391 .fill(window.clone());
392 painter
393 .aabb(Aabb {
394 min: Vec2::new(plot_center.x + length, plot_center.y - 2)
395 .with_z(floor_level + 2),
396 max: Vec2::new(plot_center.x + length + 1, plot_center.y + 3)
397 .with_z(floor_level + 7),
398 })
399 .clear();
400
401 painter
402 .aabb(Aabb {
403 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 3)
404 .with_z(floor_level + 2),
405 max: Vec2::new(plot_center.x - length, plot_center.y + 4)
406 .with_z(floor_level + 7),
407 })
408 .fill(window.clone());
409 painter
410 .aabb(Aabb {
411 min: Vec2::new(plot_center.x - length - 1, plot_center.y - 2)
412 .with_z(floor_level + 2),
413 max: Vec2::new(plot_center.x - length, plot_center.y + 3)
414 .with_z(floor_level + 7),
415 })
416 .clear();
417 // furniture
418 if workshops < 1 {
419 painter
420 .aabb(Aabb {
421 min: (plot_center - 1).with_z(floor_level + 1),
422 max: (plot_center + 2).with_z(floor_level + 2),
423 })
424 .fill(brick.clone());
425 painter
426 .aabb(Aabb {
427 min: plot_center.with_z(floor_level),
428 max: (plot_center + 1).with_z(floor_level + 1),
429 })
430 .fill(Fill::Block(Block::air(SpriteKind::FireBlock)));
431
432 painter
433 .aabb(Aabb {
434 min: plot_center.with_z(floor_level + 1),
435 max: (plot_center + 1).with_z(floor_level + 2),
436 })
437 .clear();
438 let mut stations = vec![
439 SpriteKind::CraftingBench,
440 SpriteKind::Forge,
441 SpriteKind::SpinningWheel,
442 SpriteKind::TanningRack,
443 SpriteKind::CookingPot,
444 SpriteKind::Cauldron,
445 SpriteKind::Loom,
446 SpriteKind::Anvil,
447 SpriteKind::DismantlingBench,
448 SpriteKind::RepairBench,
449 ];
450 'outer: for d in 0..3 {
451 for dir in CARDINALS {
452 if stations.is_empty() {
453 break 'outer;
454 }
455 let position = plot_center + dir * (3 + d * 2);
456 let cr_station = stations.swap_remove(
457 RandomField::new(0).get(position.with_z(base)) as usize
458 % stations.len(),
459 );
460 painter.sprite(position.with_z(floor_level + 1), cr_station);
461 }
462 }
463 workshops += 1;
464 // forge tools
465 for d in 0..2 {
466 let pos = Vec2::new(
467 plot_center.x - 5 + (d * 10),
468 plot_center.y - length + (d * ((2 * length) - 1)),
469 );
470 painter
471 .aabb(Aabb {
472 min: Vec2::new(pos.x - 2, pos.y - (4 * d))
473 .with_z(floor_level + 1),
474 max: Vec2::new(pos.x + 3, pos.y + 5 - (4 * d))
475 .with_z(floor_level + 4),
476 })
477 .clear();
478 painter.rotated_sprite(
479 pos.with_z(floor_level + 1),
480 SpriteKind::Hearth,
481 (4 * d) as u8,
482 );
483 }
484 // forge tools
485 for d in 0..2 {
486 let pos = Vec2::new(
487 plot_center.x + 5 - (d * 10),
488 plot_center.y - length + (d * ((2 * length) - 1)),
489 );
490 painter
491 .aabb(Aabb {
492 min: Vec2::new(pos.x - 2, pos.y - (4 * d))
493 .with_z(floor_level + 1),
494 max: Vec2::new(pos.x + 3, pos.y + 5 - (4 * d))
495 .with_z(floor_level + 4),
496 })
497 .clear();
498 painter.rotated_sprite(
499 pos.with_z(floor_level + 1),
500 SpriteKind::ForgeTools,
501 (4 * d) as u8,
502 );
503 }
504 } else {
505 match (RandomField::new(0).get(plot_center.with_z(floor_level))) % 3 {
506 0 => {
507 // living room
508 // distribute small sprites
509 for dir in LOCALITY {
510 let pos = plot_center + dir * ((length / 3) - 1);
511 painter.owned_resource_sprite(
512 pos.with_z(floor_level + 1),
513 match (RandomField::new(0).get(pos.with_z(floor_level)))
514 % 9
515 {
516 0 => SpriteKind::WardrobeSingleMesa,
517 1 => SpriteKind::CoatrackMetalWoodland,
518 2 => SpriteKind::MirrorMesa,
519 3 => SpriteKind::CushionArabic,
520 4 => SpriteKind::JugArabic,
521 5 => SpriteKind::SepareArabic,
522 6 => SpriteKind::Crate,
523 7 => SpriteKind::Bowl,
524 _ => SpriteKind::MesaLantern,
525 },
526 0,
527 );
528 }
529 // beds & wardrobes
530 for d in 0..2 {
531 let pos = Vec2::new(
532 plot_center.x - length + 6 + (d * ((2 * length) - 12)),
533 plot_center.y - length + 5 + (d * ((2 * length) - 10)),
534 );
535 painter
536 .aabb(Aabb {
537 min: Vec2::new(
538 pos.x - 1 - (3 * d),
539 pos.y - 1 - (3 * d),
540 )
541 .with_z(floor_level + 1),
542 max: Vec2::new(
543 pos.x + 5 - (3 * d),
544 pos.y + 5 - (3 * d),
545 )
546 .with_z(floor_level + 5),
547 })
548 .clear();
549 let dir = Dir2::from_vec2(plot_center - pos);
550 match (RandomField::new(0).get(pos.with_z(floor_level - d)))
551 % 3
552 {
553 0 => {
554 painter.rotated_sprite(
555 pos.with_z(floor_level + 1),
556 SpriteKind::WardrobeDoubleMesa,
557 (4 * d) as u8,
558 );
559 },
560 _ => {
561 painter.bed_cliff(pos.with_z(floor_level + 1), dir);
562 },
563 }
564 }
565 // bookshelfs
566 for d in 0..2 {
567 let pos = Vec2::new(
568 plot_center.x + 5 - (d * 10),
569 plot_center.y - length + (d * ((2 * length) - 1)),
570 );
571 painter.rotated_sprite(
572 pos.with_z(floor_level + 4),
573 SpriteKind::BookshelfArabic,
574 (4 * d) as u8,
575 );
576 }
577 // decor set / separe / table large
578 for d in 0..2 {
579 let pos = Vec2::new(
580 plot_center.x - length + 8 + (d * ((2 * length) - 16)),
581 plot_center.y + length - 8 + (d * ((-2 * length) + 16)),
582 );
583 painter
584 .aabb(Aabb {
585 min: Vec2::new(pos.x - 2, pos.y - 1)
586 .with_z(floor_level + 1),
587 max: Vec2::new(pos.x + 3, pos.y + 2)
588 .with_z(floor_level + 3),
589 })
590 .clear();
591 painter.sprite(
592 pos.with_z(floor_level + 1),
593 match (RandomField::new(0)
594 .get(pos.with_z(floor_level - d)))
595 % 3
596 {
597 0 => SpriteKind::TableArabicLarge,
598 1 => SpriteKind::DecorSetArabic,
599 _ => SpriteKind::SepareArabic,
600 },
601 )
602 }
603 },
604 1 => {
605 // bath
606 // wall tables with varying items
607 for d in 0..2 {
608 let pos = Vec2::new(
609 plot_center.x - 5 + (d * 10),
610 plot_center.y - length + (d * ((2 * length) - 1)),
611 );
612 painter.rotated_sprite(
613 pos.with_z(floor_level + 3),
614 SpriteKind::WallTableMesa,
615 (4 * d) as u8,
616 );
617 painter.owned_resource_sprite(
618 pos.with_z(floor_level + 4),
619 match (RandomField::new(0).get(pos.with_z(floor_level)))
620 % 3
621 {
622 0 => SpriteKind::Bowl,
623 1 => SpriteKind::VialEmpty,
624 _ => SpriteKind::JugArabic,
625 },
626 (4 * d) as u8,
627 );
628 }
629 // distribute small sprites
630 for dir in LOCALITY {
631 let pos = plot_center + dir * ((length / 3) + 1);
632 painter.owned_resource_sprite(
633 pos.with_z(floor_level + 1),
634 match (RandomField::new(0).get(pos.with_z(floor_level)))
635 % 12
636 {
637 0 => SpriteKind::DrawerWoodWoodlandS,
638 1 => SpriteKind::CoatrackWoodWoodland,
639 2 => SpriteKind::TableArabicSmall,
640 3 => SpriteKind::CushionArabic,
641 4 => SpriteKind::JugArabic,
642 5 => SpriteKind::WardrobeSingleMesa,
643 6 => SpriteKind::Crate,
644 7 => SpriteKind::DecorSetArabic,
645 8 => SpriteKind::VialEmpty,
646 9 => SpriteKind::SepareArabic,
647 10 => SpriteKind::MesaLantern,
648 _ => SpriteKind::FountainArabic,
649 },
650 0,
651 );
652 }
653 },
654 _ => {
655 // kitchen
656 // cupbooards
657 for d in 0..2 {
658 let pos = Vec2::new(
659 plot_center.x + 5 - (d * 10),
660 plot_center.y - length + (d * ((2 * length) - 1)),
661 );
662 painter.rotated_sprite(
663 pos.with_z(floor_level + 3),
664 SpriteKind::CupboardMesa,
665 (4 * d) as u8,
666 );
667 }
668 // wall tables with varying items
669 for d in 0..2 {
670 let pos = Vec2::new(
671 plot_center.x - 5 + (d * 10),
672 plot_center.y - length + (d * ((2 * length) - 1)),
673 );
674 painter.rotated_sprite(
675 pos.with_z(floor_level + 2),
676 SpriteKind::WallTableMesa,
677 (4 * d) as u8,
678 );
679 painter.owned_resource_sprite(
680 pos.with_z(floor_level + 3),
681 match (RandomField::new(0).get(pos.with_z(floor_level)))
682 % 4
683 {
684 0 => SpriteKind::Melon,
685 1 => SpriteKind::Bowl,
686 2 => SpriteKind::JugArabic,
687 _ => SpriteKind::VialEmpty,
688 },
689 (4 * d) as u8,
690 );
691 }
692 // distribute small sprites
693 for dir in LOCALITY {
694 let pos = plot_center + dir * ((length / 3) + 1);
695 painter.owned_resource_sprite(
696 pos.with_z(floor_level + 1),
697 match (RandomField::new(0).get(pos.with_z(floor_level)))
698 % 11
699 {
700 0 => SpriteKind::WardrobeSingleMesa,
701 1 => SpriteKind::Cauldron,
702 2 => SpriteKind::TableArabicSmall,
703 3 => SpriteKind::CushionArabic,
704 4 => SpriteKind::JugArabic,
705 5 => SpriteKind::Crate,
706 6 => SpriteKind::Bowl,
707 7 => SpriteKind::VialEmpty,
708 8 => SpriteKind::CookingPot,
709 9 => SpriteKind::MesaLantern,
710 10 => SpriteKind::JugAndBowlArabic,
711 _ => SpriteKind::OvenArabic,
712 },
713 0,
714 );
715 }
716 },
717 }
718 }
719 // wall lamps
720
721 let corner_pos_1 = Vec2::new(plot_center.x - length, plot_center.y - 5);
722 let corner_pos_2 = Vec2::new(plot_center.x - 5, plot_center.y - length);
723 for dir in SQUARE_4 {
724 let lamp_pos_1 = Vec2::new(
725 corner_pos_1.x + (dir.x * ((2 * length) - 1)),
726 corner_pos_1.y + (dir.y * 10),
727 )
728 .with_z(floor_level + 7);
729 painter.rotated_sprite(
730 lamp_pos_1,
731 SpriteKind::WallLampMesa,
732 (2 + (4 * dir.x)) as u8,
733 );
734 let lamp_pos_2 = Vec2::new(
735 corner_pos_2.x + (dir.x * 10),
736 corner_pos_2.y + (dir.y * ((2 * length) - 1)),
737 )
738 .with_z(floor_level + 7);
739 painter.rotated_sprite(
740 lamp_pos_2,
741 SpriteKind::WallLampMesa,
742 (4 - (4 * dir.y)) as u8,
743 );
744 }
745 }
746 // stairs
747 if floor_level > (base + 8) {
748 let stairs_level = floor_level + 1;
749 let stairs_start = plot_center + door_dir * ((2 * length) - 7);
750 let mid_dir = if door_dir.x != 0 {
751 door_dir.x
752 } else {
753 door_dir.y
754 };
755 let stairs_mid = Vec2::new(
756 plot_center.x + mid_dir * (3 * (length / 2)),
757 plot_center.y + mid_dir * (3 * (length / 2)),
758 );
759 let stairs_end = Vec2::new(
760 plot_center.x + door_dir.y * ((2 * length) - 7),
761 plot_center.y + door_dir.x * ((2 * length) - 7),
762 );
763 let rope_pos = Vec2::new(
764 plot_center.x + mid_dir * ((3 * (length / 2)) + 2),
765 plot_center.y + mid_dir * ((3 * (length / 2)) + 2),
766 );
767
768 painter
769 .cylinder(Aabb {
770 min: (stairs_start - 6).with_z(stairs_level - 1),
771 max: (stairs_start + 6).with_z(stairs_level),
772 })
773 .fill(wood.clone());
774
775 painter
776 .cylinder(Aabb {
777 min: (stairs_mid - 6).with_z(stairs_level - (height / 2) - 1),
778 max: (stairs_mid + 6).with_z(stairs_level - (height / 2)),
779 })
780 .fill(wood.clone());
781
782 painter
783 .cylinder(Aabb {
784 min: (stairs_end - 6).with_z(stairs_level - height - 1),
785 max: (stairs_end + 6).with_z(stairs_level - height),
786 })
787 .fill(wood.clone());
788
789 for n in 0..2 {
790 let stairs = painter
791 .line(
792 stairs_start.with_z(stairs_level + (n * 2)),
793 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
794 4.0 + (n as f32 / 2.0),
795 )
796 .union(painter.line(
797 stairs_mid.with_z(stairs_level - (height / 2) + (n * 2)),
798 stairs_end.with_z(stairs_level - height + (n * 2)),
799 4.0 + (n as f32 / 2.0),
800 ));
801 match n {
802 0 => stairs.fill(wood.clone()),
803 _ => stairs.clear(),
804 };
805 }
806 painter
807 .line(
808 rope_pos.with_z(stairs_level + (height / 2) - 3),
809 (plot_center - (length / 2))
810 .with_z(stairs_level + (height / 2) + 2),
811 1.5,
812 )
813 .fill(wood.clone());
814
815 painter
816 .aabb(Aabb {
817 min: rope_pos.with_z(stairs_level - (height / 2) - 1),
818 max: (rope_pos + 1).with_z(stairs_level + (height / 2) - 3),
819 })
820 .fill(rope.clone());
821 }
822 }
823 // vary next storey
824 length += -1;
825 width += -1;
826 height += -1;
827 floor_level += height;
828 mem::swap(&mut length, &mut width);
829 }
830 }
831 // spawn campfire next to some clifftowers
832 if self.campfire {
833 let campfire_pos = (plot_center - 20).with_z(self.alt + 18);
834 painter.spawn(
835 EntityInfo::at(campfire_pos.map(|e| e as f32))
836 .into_special(SpecialEntity::Waypoint),
837 );
838 }
839 }
840}