use crate::app::settings::GameSettings;
use crate::app::{Playback, Screen, menu_ui, palette};
use bevy::prelude::*;
#[derive(Component)]
pub struct FieldGuide;
#[derive(Component)]
pub struct FieldGuideNote(usize);
#[derive(Component)]
pub struct GoldenEntry;
const LEGEND_CRAB_PX: f32 = 26.0;
const GUIDE_GAP: f32 = 10.0;
const GUIDE_PAD_X: f32 = 12.0;
use crate::app::palette::LEGEND_SAND;
const KINDS: [crate::sim::CrabKind; 6] = [
crate::sim::CrabKind::Common,
crate::sim::CrabKind::Juvenile,
crate::sim::CrabKind::Giant,
crate::sim::CrabKind::Molting,
crate::sim::CrabKind::Golden,
crate::sim::CrabKind::Sparkling,
];
fn field_guide_note(tr: &crate::app::i18n::Tr, i: usize, board: &crate::sim::Board) -> String {
let worth = board.crab_value(KINDS[i]);
let wheel_off = KINDS[i] == crate::sim::CrabKind::Sparkling && !board.events_enabled();
let rest = crate::sim::LURE_COOLDOWN_SECONDS.to_string();
let note = if wheel_off {
String::new()
} else {
crate::app::i18n::fill(tr.crab_notes[i], &[("s", &rest)])
};
if note.is_empty() {
worth.to_string()
} else {
format!("{worth} {note}")
}
}
pub fn update_field_guide(
settings: Res<GameSettings>,
sim: Res<crate::app::Sim>,
mut notes: Query<(&FieldGuideNote, &mut Text)>,
mut golden_entries: Query<&mut Node, With<GoldenEntry>>,
mut board_shown: Local<Option<(u32, bool, bool)>>,
) {
let read = (
sim.0.crab_value(crate::sim::CrabKind::Golden),
sim.0.golden_crab_can_come(),
sim.0.events_enabled(),
);
if !settings.is_changed() && Some(read) == *board_shown {
return;
}
*board_shown = Some(read);
for mut node in &mut golden_entries {
menu_ui::set_shown(&mut node, read.1);
}
let tr = settings.tr();
for (note, mut text) in &mut notes {
menu_ui::set_text(&mut text, &field_guide_note(tr, note.0, &sim.0));
}
}
pub(super) fn spawn_field_guide(
commands: &mut Commands,
tr: &crate::app::i18n::Tr,
art: &crate::app::art::Art,
board: &crate::sim::Board,
) {
commands
.spawn((
FieldGuide,
Node {
position_type: PositionType::Absolute,
bottom: Val::Px(60.0),
left: Val::Px(0.0),
width: Val::Percent(100.0),
justify_content: JustifyContent::Center,
..default()
},
))
.with_children(|row| {
row.spawn((
Node {
column_gap: Val::Px(GUIDE_GAP),
align_items: AlignItems::Center,
padding: UiRect::axes(Val::Px(GUIDE_PAD_X), Val::Px(4.0)),
border_radius: BorderRadius::all(Val::Px(11.0)),
..default()
},
BackgroundColor(palette::PILL_FILL),
))
.with_children(|strip| {
for (i, kind) in KINDS.iter().enumerate() {
let color = crate::app::creatures::body_color(*kind);
let layer = |image: &Handle<Image>| {
(
ImageNode::new(image.clone()).with_color(color),
Node {
position_type: PositionType::Absolute,
width: Val::Percent(100.0),
height: Val::Percent(100.0),
..default()
},
)
};
let mut coin = strip.spawn((
Node {
width: Val::Px(LEGEND_CRAB_PX),
height: Val::Px(LEGEND_CRAB_PX),
border_radius: BorderRadius::MAX,
..default()
},
BackgroundColor(LEGEND_SAND),
));
coin.with_children(|icon| {
icon.spawn(layer(&art.crab));
icon.spawn(layer(&art.claw));
});
let golden = *kind == crate::sim::CrabKind::Golden;
if golden {
coin.insert(GoldenEntry);
}
let mut note = strip.spawn((
FieldGuideNote(i),
Text::new(field_guide_note(tr, i, board)),
TextFont {
font_size: FontSize::Px(menu_ui::type_scale::FINE),
..default()
},
TextLayout::no_wrap(),
TextColor(palette::PARCHMENT.with_alpha(0.85)),
));
if golden {
note.insert((GoldenEntry, Node::default()));
}
}
});
});
}
pub fn field_guide_visibility(
screen: Res<State<Screen>>,
playback: Res<Playback>,
mut guides: Query<&mut Node, With<FieldGuide>>,
) {
let wanted = field_guide_shown(*screen.get(), playback.0.is_some());
for mut node in &mut guides {
menu_ui::set_shown(&mut node, wanted);
}
}
pub(crate) fn field_guide_shown(screen: Screen, replaying: bool) -> bool {
let crabs = match screen {
Screen::Versus | Screen::Puzzle => true,
Screen::Menu
| Screen::Editor
| Screen::Lobby
| Screen::Settings
| Screen::Controls
| Screen::MatchSetup
| Screen::Achievements
| Screen::StageSelect
| Screen::Replays
| Screen::Interlude
| Screen::Language
| Screen::NewVersion => false,
};
crabs && !replaying
}
#[cfg(test)]
mod tests {
use super::*;
use crate::app::i18n::Lang;
#[test]
fn the_field_guide_quotes_the_boards_golden_worth() {
let golden = KINDS
.iter()
.position(|&k| k == crate::sim::CrabKind::Golden)
.expect("the golden crab is in the guide");
let tr = Lang::En.tr();
let mut board = crate::sim::Board::new(9, 7, 1);
assert!(field_guide_note(tr, golden, &board).starts_with("50 "));
crate::sim::set_versus_rules(&mut board, 240);
let note = field_guide_note(tr, golden, &board);
assert!(
note.starts_with(&format!("{} ", crate::sim::GOLDEN_CALLED_VALUE)),
"{note}"
);
}
#[test]
fn the_sparkling_crab_promises_nothing_where_the_wheel_is_off() {
let sparkling = KINDS
.iter()
.position(|&k| k == crate::sim::CrabKind::Sparkling)
.expect("the sparkling crab is in the guide");
let tr = Lang::En.tr();
let mut board = crate::sim::Board::new(9, 7, 1);
board.set_events_enabled(false);
let worth = board.crab_value(crate::sim::CrabKind::Sparkling);
assert_eq!(field_guide_note(tr, sparkling, &board), worth.to_string());
board.set_events_enabled(true);
let note = field_guide_note(tr, sparkling, &board);
assert!(note.ends_with(tr.crab_notes[sparkling]), "{note}");
}
#[test]
fn the_molting_crab_says_how_long_its_lure_rests() {
let molting = KINDS
.iter()
.position(|&k| k == crate::sim::CrabKind::Molting)
.expect("the molting crab is in the guide");
let board = crate::sim::Board::new(9, 7, 1);
let rest = crate::sim::LURE_COOLDOWN_SECONDS.to_string();
for lang in crate::app::i18n::ALL_LANGS {
let note = field_guide_note(lang.tr(), molting, &board);
assert!(note.contains(&rest), "{lang:?}: {note}");
assert!(!note.contains('{'), "{lang:?}: {note}");
}
}
#[test]
fn the_field_guide_fits_between_the_side_panels() {
use crate::app::i18n::metrics::text_px;
use crate::app::settings::DESIGN_W;
use crate::app::side_panels::SIDEBAR_W;
let board = crate::sim::Board::new(9, 7, 1);
let room = DESIGN_W - 2.0 * SIDEBAR_W;
for lang in crate::app::i18n::ALL_LANGS {
let notes: f32 = (0..KINDS.len())
.map(|i| {
text_px(
&field_guide_note(lang.tr(), i, &board),
menu_ui::type_scale::FINE,
)
})
.sum();
let gaps = (2 * KINDS.len() - 1) as f32 * GUIDE_GAP;
let width = 2.0 * GUIDE_PAD_X + KINDS.len() as f32 * LEGEND_CRAB_PX + gaps + notes;
assert!(width <= room, "{lang:?}: the guide is {width}px of {room}");
}
}
}