use super::*;
#[derive(Resource, Default)]
pub struct CustomBeaches(pub Vec<Beach>);
pub struct Beach {
pub level: crate::sim::Level,
pub(super) wire: Vec<u8>,
}
impl Beach {
pub(crate) fn new(level: crate::sim::Level) -> Beach {
let wire = crate::lzw::compress(level.to_text().as_bytes(), 8);
Beach { level, wire }
}
pub fn too_big_to_send(&self) -> bool {
self.wire.len() > crate::transport::MAX_BEACH_BYTES
}
}
pub fn refresh_custom_beaches(mut beaches: ResMut<CustomBeaches>) {
beaches.0 = crate::app::campaign::custom_arenas(crate::app::campaign::load_custom_levels())
.into_iter()
.map(Beach::new)
.collect();
}
impl CustomBeaches {
pub fn shelf_of(&self, bytes: &[u8]) -> Option<usize> {
(!bytes.is_empty())
.then(|| self.0.iter().position(|beach| beach.wire == bytes))
.flatten()
}
pub fn fitting(&self, seats: u8) -> Vec<&Beach> {
self.0
.iter()
.filter(|beach| beach.level.fits(seats))
.collect()
}
pub fn on_shelf(&self, seats: u8, custom: usize) -> Option<usize> {
self.0
.iter()
.enumerate()
.filter(|(_, beach)| beach.level.fits(seats))
.nth(custom)
.map(|(shelf, _)| shelf)
}
pub fn among_fitting(&self, seats: u8, shelf: usize) -> Option<usize> {
let beach = self.0.get(shelf)?;
beach.level.fits(seats).then(|| {
self.0[..shelf]
.iter()
.filter(|beach| beach.level.fits(seats))
.count()
})
}
}
pub fn beaches_note(
seats: u8,
tr: &crate::app::i18n::Tr,
beaches: &CustomBeaches,
) -> Option<String> {
if beaches.0.is_empty() || !beaches.fitting(seats).is_empty() {
return None;
}
let all_skip = beaches
.0
.iter()
.all(|beach| beach.level.table_seats().is_none());
Some(match all_skip {
true => tr.map_skips_castle.to_string(),
false => fill(tr.map_none_seats, &[("n", &seats.to_string())]),
})
}
pub fn cycle_map(config: &mut MatchConfig, turn: Turn, beaches: &CustomBeaches) {
let beaches = beaches.fitting(config.seats).len();
if config.map == MapChoice::Custom {
let next = config.custom as i64 + i64::from(turn.signum());
if (0..beaches as i64).contains(&next) {
config.custom = next as usize;
return;
}
}
config.map = config.map.turned(turn);
if config.map == MapChoice::Custom {
if beaches == 0 {
config.map = config.map.turned(turn);
} else {
config.custom = match turn {
Turn::Right => 0,
Turn::Left => beaches - 1,
};
}
}
}
pub fn map_label(
config: &MatchConfig,
tr: &crate::app::i18n::Tr,
beaches: &CustomBeaches,
) -> String {
if config.map == MapChoice::Custom {
return beaches
.fitting(config.seats)
.get(config.custom)
.map_or_else(
|| map_name(tr, MapChoice::GenXl),
|beach| {
let template = match beach.too_big_to_send() {
true => tr.map_custom_local,
false => tr.map_custom,
};
fill(template, &[("n", &beach.level.name)])
},
);
}
map_name(tr, config.map)
}
pub fn map_name(tr: &crate::app::i18n::Tr, map: MapChoice) -> String {
match map {
MapChoice::Classic
| MapChoice::GenSmall
| MapChoice::GenClassic
| MapChoice::GenLarge
| MapChoice::GenXl
| MapChoice::GenOcean => tr.map_names[map.index()].to_string(),
MapChoice::Custom => tr.map_names[MapChoice::GenXl.index()].to_string(),
MapChoice::TheGates
| MapChoice::TheMoat
| MapChoice::LogJam
| MapChoice::ThreeLanes
| MapChoice::RoundTable
| MapChoice::RoundTheWorld
| MapChoice::Duel
| MapChoice::TheFountain
| MapChoice::FourDoors
| MapChoice::GullRoad
| MapChoice::BackToBack => {
let (w, h) = map.size();
format!("{} ({w}x{h})", map_title(tr, map))
}
MapChoice::Surprise => map_title(tr, map),
}
}
pub fn map_title(tr: &crate::app::i18n::Tr, map: MapChoice) -> String {
match (map, map.handmade()) {
(_, Some(handmade)) => tr.setup_versus_maps[handmade].to_string(),
(MapChoice::Surprise, None) => tr.setup_surprise.to_string(),
(map, None) => tr.setup_map_names[map.index()].to_string(),
}
}
pub fn map_about(tr: &crate::app::i18n::Tr, map: MapChoice) -> &'static str {
match map {
MapChoice::Classic
| MapChoice::GenSmall
| MapChoice::GenClassic
| MapChoice::GenLarge
| MapChoice::GenXl
| MapChoice::GenOcean => tr.setup_map_about[map.index()],
MapChoice::TheGates
| MapChoice::TheMoat
| MapChoice::LogJam
| MapChoice::ThreeLanes
| MapChoice::RoundTable
| MapChoice::RoundTheWorld
| MapChoice::Duel
| MapChoice::TheFountain
| MapChoice::FourDoors
| MapChoice::GullRoad
| MapChoice::BackToBack => tr.setup_versus_about[map.handmade().unwrap_or(0)],
MapChoice::Surprise => tr.setup_surprise_sub,
MapChoice::Custom => tr.setup_custom_about,
}
}
pub fn beach_in_play<L: std::borrow::Borrow<crate::sim::Level>>(
map: MapChoice,
level: Option<L>,
seats: u8,
) -> Option<L> {
level.filter(|level| map == MapChoice::Custom && level.borrow().fits(seats))
}
pub fn beach_on_air<'a>(
config: &MatchConfig,
seats: u8,
beaches: &'a CustomBeaches,
) -> Option<&'a Beach> {
let beach = beaches.fitting(config.seats).get(config.custom).copied()?;
beach_in_play(config.map, Some(&beach.level), seats)?;
(!beach.too_big_to_send()).then_some(beach)
}
pub fn beach_bytes(config: &MatchConfig, seats: u8, beaches: &CustomBeaches) -> Vec<u8> {
if let Some(beach) = beaches.fitting(config.seats).get(config.custom)
&& config.map == MapChoice::Custom
&& beach.too_big_to_send()
{
warn!(
"{:?} is too big to send ({} bytes packed, {} allowed): \
the round falls back to a generated beach",
beach.level.name,
beach.wire.len(),
crate::transport::MAX_BEACH_BYTES
);
}
beach_on_air(config, seats, beaches).map_or_else(Vec::new, |beach| beach.wire.clone())
}
pub fn beach_from(bytes: &[u8]) -> Option<crate::sim::Level> {
let text = String::from_utf8(crate::lzw::decompress(bytes, 8)?).ok()?;
crate::sim::Level::parse(&text).ok()
}