1use super::*;
2use crate::{
3 ColumnSample, Land,
4 util::{LOCALITY, RandomField, Sampler},
5};
6use common::terrain::{Block, BlockKind};
7use enumset::EnumSet;
8use strum::IntoEnumIterator;
9use util::sprites::PainterSpriteExt;
10use vek::*;
11
12#[derive(Clone, Copy, PartialEq, Eq)]
13pub enum RoadLights {
14 Default,
15 Terracotta,
16}
17
18#[derive(Clone, Copy, PartialEq, Eq)]
19pub enum RoadMaterial {
20 Dirt,
21 Cobblestone,
22 Sandstone,
23 Marble,
24}
25
26#[derive(Clone, Copy, PartialEq, Eq)]
27pub struct RoadKind {
28 pub lights: RoadLights,
29 pub material: RoadMaterial,
30}
31
32impl RoadKind {
33 pub fn place_light(&self, pos: Vec3<i32>, dir: Dir2, painter: &Painter) {
35 let wood_corner = Fill::Brick(BlockKind::Wood, Rgb::new(86, 50, 50), 10);
36 painter
37 .column(pos.xy(), pos.z - 4..pos.z)
38 .sample_with_column(|p, col| p.z >= col.alt as i32)
39 .fill(wood_corner);
40 match self.lights {
41 RoadLights::Default => painter.lanternpost_wood(pos, dir),
42 RoadLights::Terracotta => painter.sprite(pos, SpriteKind::LampTerracotta),
43 }
44 }
45
46 pub fn block(&self, col: &ColumnSample, wpos: Vec3<i32>, dir: Dir2) -> Block {
47 match self.material {
48 RoadMaterial::Dirt => Block::new(
49 BlockKind::Earth,
50 crate::sim::Path::default()
51 .surface_color((col.sub_surface_color * 255.0).as_(), wpos),
52 ),
53 RoadMaterial::Cobblestone => Block::new(
54 BlockKind::Rock,
55 (col.stone_col.as_() * 0.4
56 + (col.sub_surface_color * 0.4 * 255.0)
57 + (RandomField::new(15).get(wpos / (dir.orthogonal().to_vec2() + 1).with_z(1))
58 % 20) as f32)
59 .as_(),
60 ),
61 RoadMaterial::Sandstone => Block::new(
62 BlockKind::Rock,
63 (col.stone_col.as_() * 0.2
64 + (col.surface_color * 0.5 * 255.0)
65 + (RandomField::new(15).get(wpos / (dir.orthogonal().to_vec2() + 1).with_z(1))
66 % 20) as f32)
67 .as_(),
68 ),
69 RoadMaterial::Marble => Block::new(
70 BlockKind::Rock,
71 Rgb::broadcast((col.marble_small * 0.5 + 0.4) * 255.0).as_(),
72 ),
73 }
74 }
75}
76
77pub struct Road {
79 pub path: Path<Vec2<i32>>,
80 pub kind: RoadKind,
81}
82
83impl Structure for Road {
84 #[cfg(feature = "dyn-lib")]
85 #[unsafe(export_name = "as_dyn_structure_road")]
86 fn as_dyn_outer(&self) -> Option<(&dyn Structure, &'static str)> {
87 Some((Self::as_dyn_impl(self), "as_dyn_structure_road"))
88 }
89
90 fn render_ordering(&self) -> u32 { 2 }
91
92 fn render_inner(&self, site: &Site, _land: &Land, painter: &Painter) {
93 let field = RandomField::new(76237);
94
95 for p in self.path.iter() {
96 if (p.y + p.x) % 3 != 0 {
97 continue;
98 }
99
100 let current_tile = site.tiles.get(*p);
101 let TileKind::Road {
102 w,
103 a: this_a,
104 b: this_b,
105 alt,
106 } = current_tile.kind
107 else {
108 continue;
109 };
110
111 let center = site.tile_center_wpos(*p);
112
113 let light_wpos = |dir: Dir2| {
114 let width = w as i32 * 2 - 1 - (dir.signum() + 1) / 2;
115 center + dir.to_vec2() * width
116 };
117 let available_dirs: EnumSet<Dir2> = Dir2::iter()
118 .filter(|dir| {
119 let light_wpos = light_wpos(*dir);
120 let tpos = site.wpos_tile_pos(light_wpos);
121 [tpos, tpos + dir.to_vec2()].into_iter().all(|tpos| {
122 let tile = site.tiles.get(tpos);
123 tile.is_natural()
124 || if let TileKind::Road { a, b, .. } = tile.kind {
125 current_tile.plot == tile.plot && this_a == a && this_b == b
126 } else {
127 false
128 }
129 })
130 })
131 .collect();
132
133 if available_dirs.is_empty() {
134 continue;
135 }
136
137 let i = field.get(p.with_z(11)) as usize % available_dirs.len();
138 let Some(dir) = available_dirs.iter().nth(i) else {
139 continue;
140 };
141
142 let wpos = light_wpos(dir);
143
144 let wpos = wpos.with_z(alt as i32);
145 self.kind.place_light(wpos, -dir, painter);
146 }
147 }
148
149 fn rel_terrain_offset(&self, col: &ColumnSample) -> i32 {
150 (col.riverless_alt as i32).max(col.water_level as i32 + 1)
151 }
152
153 fn terrain_surface_at_inner(
154 &self,
155 wpos: Vec2<i32>,
156 old: Block,
157 _rng: &mut ChaCha8Rng,
158 col: &ColumnSample,
159 z_off: i32,
160 site: &Site,
161 ) -> Option<Block> {
162 let z = self.rel_terrain_offset(col) + z_off;
163 if col.alt < col.water_level && z < 0 {
164 return None;
165 }
166
167 if z_off <= 0 {
168 let tpos = site.wpos_tile_pos(wpos);
169 let mut near_roads = LOCALITY.iter().filter_map(|rpos| {
170 let tile = site.tiles.get(tpos + rpos);
171 if let TileKind::Road { a, b, w, .. } = &tile.kind {
172 if let Some(PlotKind::Road(Road { path, .. })) =
173 tile.plot.map(|p| &site.plot(p).kind)
174 {
175 let is_start = *a == 0;
176 let is_end = *b == path.len() as u16 - 1;
177 let a = path.nodes()[*a as usize];
178 let b = path.nodes()[*b as usize];
179 let path_dir = Dir2::from_vec2(b - a);
180 Some((
181 LineSegment2 {
182 start: site.tile_center_wpos(a)
183 - if is_start {
184 path_dir.to_vec2() * TILE_SIZE as i32 / 2
185 } else {
186 Vec2::zero()
187 },
188 end: site.tile_center_wpos(b)
189 + if is_end {
190 path_dir.to_vec2() * TILE_SIZE as i32 / 2
191 } else {
192 Vec2::zero()
193 },
194 }
195 .as_(),
196 *w,
197 ))
198 } else {
199 None
200 }
201 } else {
202 None
203 }
204 });
205
206 let wposf = wpos.map(|e| e as f32);
207 if let Some((line, _)) =
208 near_roads.find(|(line, w)| line.distance_to_point(wposf) < *w as f32 * 2.0)
209 {
210 if z_off == 0 {
211 Some(old.into_vacant())
212 } else {
213 let dir = Dir2::from_vec2((line.start - line.end).as_());
214 Some(self.kind.block(col, wpos.with_z(z), dir))
215 }
216 } else {
217 None
218 }
219 } else if old.is_fluid() || old.kind() == BlockKind::Snow || old.is_terrain() {
220 Some(old.into_vacant())
221 } else {
222 None
223 }
224 }
225}