use crate::diagnostic::Diagnostic;
use crate::envelope::Campaign;
pub const FLOOR: u8 = 10;
pub const CEILING: u8 = 32;
pub const CHUNK_BLOCKS: u32 = 16;
pub fn chunks(c: &Campaign) -> u8 {
c.world.content.view_distance.unwrap_or(FLOOR)
}
pub fn served_radius_blocks(chunks: u8) -> f64 {
f64::from(u32::from(chunks) * CHUNK_BLOCKS)
}
pub fn chunks_for(distance: f64) -> u8 {
let n = (distance / f64::from(CHUNK_BLOCKS)).ceil();
if n < f64::from(FLOOR) {
FLOOR
} else if n > f64::from(CEILING) {
CEILING
} else {
n as u8
}
}
pub fn distance(a: [i64; 3], b: [i64; 3]) -> f64 {
let d = [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
((d[0] * d[0] + d[1] * d[1] + d[2] * d[2]) as f64).sqrt()
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Binding {
pub chunks: u8,
pub declared: bool,
pub sightlines: usize,
pub views: usize,
pub beyond: usize,
}
impl Binding {
#[must_use]
pub fn line(&self) -> String {
format!(
"view distance binding: {n} chunk(s) ({how}) serve {r} blocks in every direction; \
{s} sightline(s) and {v} view(s) judged against it, {b} beyond it.",
n = self.chunks,
how = if self.declared {
"declared"
} else {
"the engine's floor, undeclared"
},
r = served_radius_blocks(self.chunks),
s = self.sightlines,
v = self.views,
b = self.beyond,
)
}
}
pub fn checks(c: &Campaign, d: &mut Vec<Diagnostic>) -> Binding {
let declared = c.world.content.view_distance;
let mut b = Binding {
chunks: FLOOR,
declared: declared.is_some(),
..Binding::default()
};
match declared {
Some(n) if (FLOOR..=CEILING).contains(&n) => b.chunks = n,
Some(n) => d.push(Diagnostic::error(
crate::diagnostic::codes::VIEW_BEYOND_SERVED,
"world",
"/content/view_distance".to_string(),
format!(
"`view_distance` = {n} chunks cannot be served: the pinned server serves at most \
{CEILING} chunks, and the engine's floor is {FLOOR} (every proof is written \
against it, and a smaller number only takes views away). Declare a value in \
{FLOOR}..={CEILING}, or drop the field for the floor."
),
)),
None => {}
}
let radius = served_radius_blocks(b.chunks);
let Some(plan) = c.site_plan.as_ref().map(|p| &p.content) else {
return b;
};
for (i, s) in plan.sightlines.iter().enumerate() {
b.sightlines += 1;
let len = distance(s.from, s.to);
if len > radius {
b.beyond += 1;
d.push(Diagnostic::error(
crate::diagnostic::codes::VIEW_BEYOND_SERVED,
"site-plan",
format!("/content/sightlines/{i}"),
beyond_message(
&format!("the vista `{}`", s.edge),
len,
b.chunks,
b.declared,
),
));
}
}
for (i, v) in plan.views.iter().enumerate() {
b.views += 1;
let len = distance(v.eye, v.look_at);
if len > radius {
b.beyond += 1;
d.push(Diagnostic::error(
crate::diagnostic::codes::VIEW_BEYOND_SERVED,
"site-plan",
format!("/content/views/{i}"),
beyond_message(&format!("the view `{}`", v.id), len, b.chunks, b.declared),
));
}
}
b
}
pub fn beyond_message(what: &str, length: f64, chunks: u8, declared: bool) -> String {
let radius = served_radius_blocks(chunks);
let need = chunks_for(length);
let how = if declared {
format!("the declared `world.view_distance` of {chunks} chunks")
} else {
format!("the engine's floor of {chunks} chunks (nothing declared)")
};
let remedy = if need > chunks {
format!(
"Declare `world.view_distance: {need}` (the fewest chunks that serve {length:.0} \
blocks), or bring the two ends within {radius:.0} blocks of each other."
)
} else {
format!(
"Bring the two ends within {radius:.0} blocks of each other: {length:.0} blocks is \
past what the pinned server can serve at any view distance ({CEILING} chunks)."
)
};
format!(
"{what} reaches {length:.1} blocks, and {how} serves {radius:.0} blocks in every \
direction — what it looks at is never sent to the client, so the view cannot render. \
{remedy}"
)
}