use bevy::prelude::*;
use bevy_replique::prelude::*;
const START_NODE: &str = "start";
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(AssetPlugin {
file_path: "assets".to_string(),
..default()
}))
.add_plugins(RepliquePlugin)
.register_replique_feature_value::<Stats>("Stats")
.register_replique_feature::<Wounded>("Wounded")
.add_dialogue_function(party_size)
.add_dialogue_function(richest)
.add_dialogue_command(hurt)
.init_resource::<Waiting>()
.add_systems(Startup, setup)
.add_systems(Update, (show_line, show_stats, show_finished, handle_input))
.run();
}
#[derive(RepliqueValue, Component, Debug, Clone, Copy, PartialEq, Eq)]
enum Character {
Alice,
Bob,
}
impl Character {
fn name(self) -> &'static str {
match self {
Self::Alice => "Alice",
Self::Bob => "Bob",
}
}
}
#[derive(RepliqueValue, Component, Debug, Clone, Copy)]
struct Stats {
hp: i64,
gold: i64,
}
const WOUNDED_UNDER: i64 = 10;
#[derive(Component)]
struct Wounded;
#[replique_function]
fn party_size(In(()): In<()>, characters: Query<&Character>) -> i64 {
characters.iter().count() as i64
}
#[replique_function]
fn richest(In(()): In<()>, characters: Query<(&Character, &Stats)>) -> Result<Character, String> {
characters
.iter()
.max_by_key(|(_, stats)| stats.gold)
.map(|(character, _)| *character)
.ok_or_else(|| "nobody is on the scene".to_owned())
}
#[replique_command]
fn hurt(
In((who, damage)): In<(Character, i64)>,
mut characters: Query<(Entity, &Character, &mut Stats)>,
mut commands: Commands,
) {
for (entity, character, mut stats) in &mut characters {
if *character == who {
stats.hp = (stats.hp - damage).max(0);
if stats.hp < WOUNDED_UNDER {
commands.entity(entity).insert(Wounded);
}
}
}
}
#[derive(Resource, Default)]
enum Waiting {
#[default]
Nothing,
Line(DialogueToken),
}
#[derive(Component)]
struct StatText(Character);
#[derive(Component)]
struct LineText;
#[derive(Component)]
struct HintText;
fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut start: MessageWriter<StartDialogue>,
) {
commands.spawn(Camera2d);
for (who, stats) in [
(Character::Alice, Stats { hp: 14, gold: 3 }),
(Character::Bob, Stats { hp: 11, gold: 12 }),
] {
commands.spawn((who, RepliqueSubject::new(who.name()), stats));
}
let dialogue: Handle<RepliqueDialogue> = asset_server.load("dialogue/feature.rep");
let runner = commands.spawn(DialogueRunner::new(dialogue)).id();
start.write(StartDialogue {
runner,
node: START_NODE.into(),
});
commands.spawn((
Node {
width: percent(100),
height: percent(100),
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::SpaceBetween,
padding: UiRect::all(px(32)),
..default()
},
children![
(
Node {
flex_direction: FlexDirection::Column,
row_gap: px(8),
..default()
},
children![
(
Text::new(""),
TextFont::from_font_size(20.0),
TextColor(Color::srgb(0.92, 0.68, 0.72)),
StatText(Character::Alice),
),
(
Text::new(""),
TextFont::from_font_size(20.0),
TextColor(Color::srgb(0.62, 0.75, 0.94)),
StatText(Character::Bob),
),
],
),
(Text::new(""), TextFont::from_font_size(28.0), LineText),
(
Text::new("[Space] continue"),
TextFont::from_font_size(20.0),
TextColor(Color::srgb(0.7, 0.7, 0.8)),
HintText,
),
],
));
}
fn show_stats(characters: Query<(&Character, &Stats)>, mut texts: Query<(&StatText, &mut Text)>) {
for (StatText(who), mut text) in &mut texts {
let Some((_, stats)) = characters.iter().find(|(c, _)| *c == who) else {
continue;
};
text.0 = format!("{} hp {} gold {}", who.name(), stats.hp, stats.gold);
}
}
fn show_line(
mut lines: MessageReader<DialogueLine>,
mut waiting: ResMut<Waiting>,
mut text: Single<&mut Text, With<LineText>>,
) {
for line in lines.read() {
text.0 = match &line.speaker {
Some(speaker) => format!("{speaker}: {}", line.text),
None => line.text.clone(),
};
*waiting = Waiting::Line(line.token);
}
}
fn show_finished(
mut finished: MessageReader<DialogueFinished>,
mut waiting: ResMut<Waiting>,
mut hint: Single<&mut Text, With<HintText>>,
) {
for _ in finished.read() {
hint.0 = "End of dialogue.".to_string();
*waiting = Waiting::Nothing;
}
}
fn handle_input(
keys: Res<ButtonInput<KeyCode>>,
mut waiting: ResMut<Waiting>,
mut resume: MessageWriter<ResumeDialogue>,
) {
if let Waiting::Line(token) = *waiting
&& keys.just_pressed(KeyCode::Space)
{
resume.write(ResumeDialogue {
token,
input: ResumeInput::Advance,
});
*waiting = Waiting::Nothing;
}
}