Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client #103

Merged
merged 7 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct Assets {
pub angry: Texture2D,

// action icons
pub movement: Texture2D,
pub move_units: Texture2D,
pub log: Texture2D,
pub end_turn: Texture2D,
pub advances: Texture2D,
Expand Down Expand Up @@ -63,7 +63,7 @@ impl Assets {
advances: load_png(include_bytes!("../assets/lab-svgrepo-com.png")),
end_turn: load_png(include_bytes!("../assets/hour-glass-svgrepo-com.png")),
log: load_png(include_bytes!("../assets/scroll-svgrepo-com.png")),
movement: load_png(include_bytes!("../assets/route-start-svgrepo-com.png")),
move_units: load_png(include_bytes!("../assets/route-start-svgrepo-com.png")),
settle: load_png(include_bytes!("../assets/castle-manor-14-svgrepo-com.png")),

// UI
Expand Down
245 changes: 155 additions & 90 deletions client/src/city_ui.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate};
use crate::collect_ui::{possible_resource_collections, CollectResources};
use crate::construct_ui::{new_building_positions, ConstructionPayment, ConstructionProject};
use crate::happiness_ui::{add_increase_happiness, IncreaseHappiness};
use crate::hex_ui::Point;
use crate::layout_ui::draw_scaled_icon;
use crate::map_ui::{move_units_button, show_map_action_buttons};
use crate::recruit_unit_ui::RecruitAmount;
use crate::resource_ui::ResourceType;
use crate::{hex_ui, player_ui};
use macroquad::prelude::*;
use std::ops::Add;

use server::city::{City, MoodState};
use server::city_pieces::Building;
use server::game::Game;
use server::player::Player;
use server::position::Position;
use server::unit::{UnitType, Units};

use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate};
use crate::collect_ui::{possible_resource_collections, CollectResources};
use crate::construct_ui::{building_positions, ConstructionPayment, ConstructionProject};
use crate::hex_ui::Point;
use crate::layout_ui::{bottom_center_texture, draw_scaled_icon, icon_pos};
use crate::recruit_unit_ui::RecruitAmount;
use crate::resource_ui::ResourceType;
use crate::{hex_ui, player_ui};
use std::ops::Add;

pub struct CityMenu {
pub player: ShownPlayer,
Expand Down Expand Up @@ -45,79 +45,77 @@ impl CityMenu {
}
}

pub type IconActionVec<'a> = Vec<(&'a Texture2D, String, Box<dyn Fn() -> StateUpdate + 'a>)>;
pub type IconAction<'a> = (&'a Texture2D, String, Box<dyn Fn() -> StateUpdate + 'a>);

pub type IconActionVec<'a> = Vec<IconAction<'a>>;

pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> StateUpdate {
let city = menu.get_city(game);
let pos = menu.city_position;

let can_play = menu.player.can_play_action && menu.is_city_owner() && city.can_activate();
if !can_play {
return StateUpdate::None;
}
let mut icons: IconActionVec<'a> = vec![];
icons.push((
&state.assets.resources[&ResourceType::Food],
"Collect Resources".to_string(),
Box::new(|| {
StateUpdate::OpenDialog(ActiveDialog::CollectResources(CollectResources::new(
menu.player.index,
menu.city_position,
possible_resource_collections(game, menu.city_position, menu.city_owner_index),
)))
}),
));
icons.push((
&state.assets.units[&UnitType::Infantry],
"Recruit Units".to_string(),
Box::new(|| {
RecruitAmount::new_selection(
game,
menu.player.index,
menu.city_position,
Units::empty(),
None,
&[],
)

let base_icons: IconActionVec<'a> = vec![
increase_happiness_button(game, menu, state),
move_units_button(game, pos, &menu.player, state),
Some(collect_resources_button(game, menu, state)),
Some(recruit_button(game, menu, state)),
]
.into_iter()
.flatten()
.collect();

let buildings: IconActionVec<'a> = building_icons(game, menu, state);

let wonders: IconActionVec<'a> = wonder_icons(game, menu, state);

show_map_action_buttons(
state,
&vec![base_icons, buildings, wonders]
.into_iter()
.flatten()
.collect(),
)
}

fn increase_happiness_button<'a>(
game: &'a Game,
menu: &'a CityMenu,
state: &'a State,
) -> Option<IconAction<'a>> {
let city = menu.get_city(game);
if city.mood_state == MoodState::Happy {
return None;
}
Some((
&state.assets.resources[&ResourceType::MoodTokens],
"Increase happiness".to_string(),
Box::new(move || {
let player = &menu.player;
let mut happiness = IncreaseHappiness::new(player.get(game));
let mut target = city.mood_state.clone();
while target != MoodState::Happy {
happiness = add_increase_happiness(city, &happiness);
target = target.clone().add(1);
}
StateUpdate::OpenDialog(ActiveDialog::IncreaseHappiness(happiness))
}),
));
))
}

fn wonder_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> IconActionVec<'a> {
let owner = menu.get_city_owner(game);
let city = menu.get_city(game);

for (building, name) in building_names() {
if menu.is_city_owner()
&& menu.player.can_play_action
&& city.can_construct(building, owner)
{
for pos in building_positions(building, city, &game.map) {
let tooltip = format!(
"Built {}{} for {}",
name,
pos.map_or(String::new(), |p| format!(" at {p}")),
owner.construct_cost(building, city),
);
icons.push((
&state.assets.buildings[&building],
tooltip,
Box::new(move || {
StateUpdate::OpenDialog(ActiveDialog::ConstructionPayment(
ConstructionPayment::new(
game,
name,
menu.player.index,
menu.city_position,
ConstructionProject::Building(building, pos),
),
))
}),
));
}
}
}

for w in &owner.wonder_cards {
if city.can_build_wonder(w, owner, game) {
icons.push((
owner
.wonder_cards
.iter()
.filter(|w| city.can_build_wonder(w, owner, game))
.map(|w| {
let a: IconAction<'a> = (
&state.assets.wonders[&w.name],
format!("Build wonder {}", w.name),
Box::new(move || {
Expand All @@ -131,21 +129,88 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State)
),
))
}),
));
}
}
);
a
})
.collect()
}

for (i, (icon, tooltip, action)) in icons.iter().enumerate() {
if bottom_center_texture(
state,
icon,
icon_pos(-(icons.len() as i8) / 2 + i as i8, -1),
tooltip,
) {
return action();
}
}
StateUpdate::None
fn building_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> IconActionVec<'a> {
let owner = menu.get_city_owner(game);
let city = menu.get_city(game);
building_names()
.iter()
.filter_map(|(b, _)| {
if menu.is_city_owner() && menu.player.can_play_action && city.can_construct(*b, owner)
{
Some(*b)
} else {
None
}
})
.flat_map(|b| new_building_positions(b, city, &game.map))
.map(|(b, pos)| {
let name = building_name(b);
let tooltip = format!(
"Built {}{} for {}",
name,
pos.map_or(String::new(), |p| format!(" at {p}")),
owner.construct_cost(b, city),
);
let a: IconAction<'a> = (
&state.assets.buildings[&b],
tooltip,
Box::new(move || {
StateUpdate::OpenDialog(ActiveDialog::ConstructionPayment(
ConstructionPayment::new(
game,
name,
menu.player.index,
menu.city_position,
ConstructionProject::Building(b, pos),
),
))
}),
);
a
})
.collect()
}

fn recruit_button<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> IconAction<'a> {
(
&state.assets.units[&UnitType::Infantry],
"Recruit Units".to_string(),
Box::new(|| {
RecruitAmount::new_selection(
game,
menu.player.index,
menu.city_position,
Units::empty(),
None,
&[],
)
}),
)
}

fn collect_resources_button<'a>(
game: &'a Game,
menu: &'a CityMenu,
state: &'a State,
) -> IconAction<'a> {
(
&state.assets.resources[&ResourceType::Food],
"Collect Resources".to_string(),
Box::new(|| {
let pos = menu.city_position;
StateUpdate::OpenDialog(ActiveDialog::CollectResources(CollectResources::new(
menu.player.index,
pos,
possible_resource_collections(game, pos, menu.city_owner_index),
)))
}),
)
}

pub fn city_labels(game: &Game, city: &City) -> Vec<String> {
Expand All @@ -166,11 +231,11 @@ pub fn city_labels(game: &Game, city: &City) -> Vec<String> {
.filter_map(|(b, o)| {
o.as_ref().map(|o| {
if city.player_index == *o {
building_name(b).to_string()
building_name(*b).to_string()
} else {
format!(
"{} (owned by {})",
building_name(b),
building_name(*b),
game.get_player(*o).get_name()
)
}
Expand Down Expand Up @@ -244,7 +309,7 @@ pub fn draw_city(owner: &Player, city: &City, state: &State) {
let tooltip = if matches!(state.active_dialog, ActiveDialog::CulturalInfluence) {
""
} else {
building_name(b)
building_name(*b)
};
draw_scaled_icon(
state,
Expand All @@ -271,10 +336,10 @@ pub fn building_position(city: &City, center: Point, i: i32, building: Building)
}
}

pub fn building_name(b: &Building) -> &str {
pub fn building_name(b: Building) -> &'static str {
building_names()
.iter()
.find_map(|(b2, n)| if b == b2 { Some(n) } else { None })
.find_map(|(b2, n)| if &b == b2 { Some(n) } else { None })
.unwrap()
}

Expand Down
14 changes: 8 additions & 6 deletions client/src/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl ActiveDialog {
ActiveDialog::CulturalInfluenceResolution(c) => vec![format!(
"Pay {} culture tokens to influence {}",
c.roll_boost_cost,
building_name(&c.city_piece)
building_name(c.city_piece)
)],
ActiveDialog::FreeAdvance => {
vec!["Click on an advance to take it for free".to_string()]
Expand Down Expand Up @@ -425,18 +425,20 @@ impl State {
}

pub fn update_from_game(&mut self, game: &Game) -> GameSyncRequest {
let dialog = self.game_state_dialog(game);
self.clear();

self.active_dialog = self.game_state_dialog(game);
self.active_dialog = dialog;
GameSyncRequest::None
}

#[must_use]
pub fn game_state_dialog(&self, game: &Game) -> ActiveDialog {
match &game.state {
GameState::Movement { .. } => {
ActiveDialog::MoveUnits(MoveSelection::new(game.active_player()))
}
GameState::Movement { .. } => ActiveDialog::MoveUnits(MoveSelection::new(
game.active_player(),
self.focused_tile,
game,
)),
GameState::CulturalInfluenceResolution(c) => {
ActiveDialog::CulturalInfluenceResolution(c.clone())
}
Expand Down
10 changes: 7 additions & 3 deletions client/src/construct_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@ use crate::recruit_unit_ui::RecruitSelection;
use crate::resource_ui::{new_resource_map, ResourceType};
use crate::select_ui::CountSelector;

pub fn building_positions(building: Building, city: &City, map: &Map) -> Vec<Option<Position>> {
pub fn new_building_positions(
building: Building,
city: &City,
map: &Map,
) -> Vec<(Building, Option<Position>)> {
if building != Building::Port {
return vec![None];
return vec![(building, None)];
}

map.tiles
.iter()
.filter_map(|(p, t)| {
if *t == Terrain::Water && city.position.is_neighbor(*p) {
Some(Some(*p))
Some((building, Some(*p)))
} else {
None
}
Expand Down
Loading
Loading