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