/*
* This file is part of laurelin_client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use crate::{
api::game::{Action, Command, Game},
game_status::PlayerState,
plugins::GameActionCreateCallEvent,
AppState, CardManifest, Global,
};
use super::{GameData, RefreshGameEvent};
pub mod log;
mod state_button;
pub struct GameUIPlugin;
impl Plugin for GameUIPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(log::LogPlugin)
.add_plugin(state_button::StateButtonPlugin)
.add_system(dev_details_ui.run_if(in_state(AppState::InGame)))
.add_system(setup_details.in_schedule(OnEnter(AppState::InGame)))
.add_systems((
update_game_state_text,
update_currency_text,
update_deck_text,
update_discard_text,
update_plays_text,
update_buys_text,
update_vp_text,
));
}
}
#[derive(Component)]
struct GameStateText;
#[derive(Component)]
struct CurrencyText;
#[derive(Component)]
struct DeckText;
#[derive(Component)]
struct DiscardText;
#[derive(Component)]
struct PlaysText;
#[derive(Component)]
struct BuysText;
#[derive(Component)]
struct VPText;
fn setup_details(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraMono-Bold.ttf");
let font_size = 40.;
let text_style = TextStyle {
font,
font_size,
color: Color::WHITE,
};
// game state
commands.spawn((
TextBundle::from_sections([
TextSection::new("State: ", text_style.clone()),
TextSection::from_style(text_style.clone()),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(20.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
GameStateText,
));
// plays
commands.spawn((
TextBundle::from_sections([
TextSection::new("Plays: ", text_style.clone()),
TextSection::from_style(text_style.clone()),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(220.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
PlaysText,
));
// buys
commands.spawn((
TextBundle::from_sections([
TextSection::new("Buys: ", text_style.clone()),
TextSection::from_style(text_style.clone()),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(180.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
BuysText,
));
// currency
commands.spawn((
TextBundle::from_sections([
TextSection::new("Currency: ", text_style.clone()),
TextSection::from_style(TextStyle {
color: Color::GOLD,
..text_style.clone()
}),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(140.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
CurrencyText,
));
// deck
commands.spawn((
TextBundle::from_sections([
TextSection::new("Deck: ", text_style.clone()),
TextSection::from_style(text_style.clone()),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(100.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
DeckText,
));
// discard
commands.spawn((
TextBundle::from_sections([
TextSection::new("Discard: ", text_style.clone()),
TextSection::from_style(text_style.clone()),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(60.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
DiscardText,
));
// vp
commands.spawn((
TextBundle::from_sections([
TextSection::new("VP: ", text_style.clone()),
TextSection::from_style(text_style),
])
.with_text_alignment(TextAlignment::Center)
.with_style(Style {
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Px(20.),
left: Val::Px(20.),
..Default::default()
},
..Default::default()
}),
VPText,
));
}
fn update_game_state_text(
mut text_query: Query<&mut Text, With<GameStateText>>,
game_data: Res<GameData>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.values().find(|p| p.state != PlayerState::Idle) else {
return;
};
text.sections[1].value = format!("{} - {:?}", player.display_name, player.state);
}
}
fn update_currency_text(
mut text_query: Query<&mut Text, With<CurrencyText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
text.sections[1].value = player.currency.to_string();
}
}
fn update_deck_text(
mut text_query: Query<&mut Text, With<DeckText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
text.sections[1].value = player.deck.len().to_string();
}
}
fn update_discard_text(
mut text_query: Query<&mut Text, With<DiscardText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
text.sections[1].value = player.discard.len().to_string();
}
}
fn update_plays_text(
mut text_query: Query<&mut Text, With<PlaysText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
if player.plays == 0 {
text.sections[1].style.color = Color::RED;
} else {
text.sections[1].style.color = Color::GREEN;
}
text.sections[1].value = player.plays.to_string();
}
}
fn update_buys_text(
mut text_query: Query<&mut Text, With<BuysText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
if player.buys == 0 {
text.sections[1].style.color = Color::RED;
} else {
text.sections[1].style.color = Color::GREEN;
}
text.sections[1].value = player.buys.to_string();
}
}
fn update_vp_text(
mut text_query: Query<&mut Text, With<VPText>>,
game_data: Res<GameData>,
global: Res<Global>,
) {
for mut text in &mut text_query {
let Some(status) = &game_data.game_status else {
return;
};
let Some(player) = status.players.get(&global.user.as_ref().unwrap().id) else {
return;
};
text.sections[1].value = player.vp.to_string();
}
}
pub fn dev_details_ui(
mut contexts: EguiContexts,
global: Res<Global>,
game_data: Res<GameData>,
card_manifest: Res<CardManifest>,
mut create_action_ev_w: EventWriter<GameActionCreateCallEvent>,
mut rg_ev_w: EventWriter<RefreshGameEvent>,
) {
egui::Window::new("Game Details")
.title_bar(false)
.show(contexts.ctx_mut(), |ui| {
let Some(game) = &game_data.game else {
// early return if game is None
return;
};
let Some(status) = &game_data.game_status else {
// early return if game_status is None
return;
};
ui.add_enabled_ui(!game_data.locked, |ui| {
#[allow(clippy::collapsible_if)]
if status.actions.is_empty() && game.host_id == global.user.as_ref().unwrap().id {
if ui.button("Init Game").clicked() {
// NOTE/FIXME: hardcoded game init
hardcoded_init(game, &mut create_action_ev_w, &card_manifest);
rg_ev_w.send(RefreshGameEvent);
}
}
if ui.button("Force Refresh").clicked() {
rg_ev_w.send(RefreshGameEvent);
}
});
ui.separator();
egui::CollapsingHeader::new("Game")
.default_open(true)
.show(ui, |ui| {
ui.label(format!("Host: {}", game.host.as_ref().unwrap().username));
ui.label(format!("Guest: {}", game.guest.as_ref().unwrap().username));
ui.label(format!("State: {:?}", game.state));
});
});
}
fn hardcoded_init(
game: &Game,
create_action_ev_w: &mut EventWriter<GameActionCreateCallEvent>,
card_manifest: &CardManifest,
) {
// first, piles
for card in &card_manifest.cards {
create_action_ev_w.send(GameActionCreateCallEvent {
action: Action::new(
&game.id,
&game.host_id,
&game.host_id,
&Command::InitSupplyPile {
card: card.clone(),
amount: 6,
},
None,
),
});
}
// second, set a player to the action phase, to start the game
create_action_ev_w.send(GameActionCreateCallEvent {
action: Action::new(
&game.id,
&game.host_id,
&game.host_id,
&Command::StartTurn {},
None,
),
});
}