veloren_world/site2/plot/
plaza.rs

1use super::*;
2use crate::{ColumnSample, Land, util::RandomField};
3use common::terrain::{Block, BlockKind};
4use enum_map::EnumMap;
5use rand::prelude::*;
6use strum::IntoEnumIterator;
7use vek::*;
8
9#[derive(Default)]
10struct CornerMeta {
11    water_alt: i32,
12    alt: i32,
13}
14
15impl CornerMeta {
16    fn water(&self) -> bool { self.alt < self.water_alt }
17}
18
19/// Represents house data generated by the `generate()` method
20pub struct Plaza {
21    pub aabr: Aabr<i32>,
22    pub kind: RoadKind,
23    corner_meta: EnumMap<Dir, CornerMeta>,
24    pub hard_alt: Option<i32>,
25    dir: Dir,
26}
27
28impl Plaza {
29    pub fn generate(
30        tile_aabr: Aabr<i32>,
31        kind: RoadKind,
32        site: &Site,
33        land: &Land,
34        index: IndexRef,
35    ) -> Self {
36        let aabr = Aabr {
37            min: site.tile_wpos(tile_aabr.min),
38            max: site.tile_wpos(tile_aabr.max),
39        };
40        let mut iaabr = aabr;
41        iaabr.max -= 1;
42
43        let get_corner_meta = |wpos| {
44            land.column_sample(wpos, index)
45                .map(|col| CornerMeta {
46                    water_alt: col.water_level as i32,
47                    alt: col.alt as i32,
48                })
49                .unwrap_or_default()
50        };
51
52        let center = get_corner_meta(iaabr.center());
53
54        let corner_meta: EnumMap<Dir, CornerMeta> = Dir::iter()
55            .map(|d| {
56                let o = d.rotated_cw();
57                let pos = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
58                (d, get_corner_meta(pos))
59            })
60            .collect();
61
62        let any_water = center.water() || corner_meta.values().any(|c| c.water());
63
64        Self {
65            aabr,
66            kind,
67            corner_meta,
68            hard_alt: if any_water {
69                Some((land.get_alt_approx(aabr.center()) as i32).max(center.water_alt + 1))
70            } else {
71                None
72            },
73            dir: *RandomField::new(51)
74                .choose(aabr.center().with_z(center.alt), &Dir::ALL)
75                .expect("Dir::ALL has len 4"),
76        }
77    }
78}
79
80impl Structure for Plaza {
81    #[cfg(feature = "use-dyn-lib")]
82    const UPDATE_FN: &'static [u8] = b"render_plaza\0";
83
84    #[cfg_attr(feature = "be-dyn-lib", unsafe(export_name = "render_plaza"))]
85    fn render_inner(&self, site: &Site, land: &Land, painter: &Painter) {
86        if let Some(alt) = self.hard_alt {
87            let wood_corner = Fill::Brick(BlockKind::Wood, Rgb::new(86, 50, 50), 10);
88            let bounds = Aabb {
89                min: self.aabr.min.with_z(alt),
90                max: self.aabr.max.with_z(alt + 1),
91            };
92            painter.aabb(bounds).fill(wood_corner.clone());
93            let mut iaabr = self.aabr;
94            let hsize = iaabr.half_size();
95            iaabr.min += 1;
96            iaabr.max -= 2;
97            for (d, meta) in self.corner_meta.iter() {
98                let o = d.rotated_cw();
99
100                let corner = d.select_aabr_with(iaabr, o.select_aabr(iaabr));
101
102                painter
103                    .column(corner, meta.alt - 5..alt)
104                    .fill(wood_corner.clone());
105
106                for (dir, index) in [(d, d.rotated_ccw()), (o, o)] {
107                    let dp = -dir;
108                    let min_alt = meta.alt.min(self.corner_meta[d.relative_to(index)].alt);
109                    for i in (0..dp.select(hsize)).step_by(8) {
110                        let p = corner + dp.to_vec2() * i;
111                        painter
112                            .column(p, min_alt - 5..alt)
113                            .fill(wood_corner.clone());
114                    }
115                }
116            }
117        }
118
119        let tile_aabr = Aabr {
120            min: site.wpos_tile_pos(self.aabr.min),
121            max: site.wpos_tile_pos(self.aabr.max) - 1,
122        };
123
124        for dir in Dir::iter() {
125            let orth = dir.orthogonal();
126
127            for i in (orth.select(tile_aabr.min) + 1)..orth.select(tile_aabr.max) {
128                let tpos = dir.select_aabr_with(tile_aabr, i);
129                if (tpos.x + tpos.y) % 3 != 0 {
130                    continue;
131                }
132
133                if site.tiles.get(tpos + dir.to_vec2()).is_empty() {
134                    let wpos = site.tile_center_wpos(tpos);
135
136                    // TODO: Not sure if this is always correct
137                    let alt = self
138                        .hard_alt
139                        .unwrap_or_else(|| land.get_alt_approx(wpos) as i32)
140                        + 1;
141                    let wpos = wpos.with_z(alt);
142                    self.kind.place_light(wpos, -dir, painter);
143                }
144            }
145        }
146
147        let rng = &mut thread_rng();
148        if rng.gen_bool(0.05) {
149            let spec = [
150                "common.entity.wild.peaceful.cat",
151                "common.entity.wild.peaceful.dog",
152            ]
153            .choose(rng)
154            .unwrap();
155            let center = self.aabr.center();
156            painter.spawn(
157                EntityInfo::at(
158                    Vec3::new(center.x, center.y, land.get_alt_approx(center) as i32).as_(),
159                )
160                .with_asset_expect(spec, rng, None)
161                .with_alignment(Alignment::Tame),
162            );
163        }
164    }
165
166    fn rel_terrain_offset(&self, col: &ColumnSample) -> i32 { col.riverless_alt as i32 }
167
168    fn terrain_surface_at<R: Rng>(
169        &self,
170        wpos: Vec2<i32>,
171        old: Block,
172        _rng: &mut R,
173        col: &ColumnSample,
174        z_off: i32,
175        _site: &Site,
176    ) -> Option<Block> {
177        let z = self.rel_terrain_offset(col) + z_off;
178        if col.water_level > col.alt || self.hard_alt.is_some_and(|alt| z < alt) {
179            return None;
180        };
181        if z_off <= 0 {
182            let block = self.kind.block(col, wpos.with_z(z), self.dir);
183            if old.is_filled() {
184                if old.is_terrain() { Some(block) } else { None }
185            } else if self.hard_alt.is_none() {
186                Some(block)
187            } else {
188                None
189            }
190        } else if old.is_fluid() || old.kind() == BlockKind::Snow || old.is_terrain() {
191            Some(old.into_vacant())
192        } else {
193            None
194        }
195    }
196}