From 5db694913b54c96f6338c4c0c29d0ae04dee207c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 13:19:25 +0100 Subject: [PATCH 1/7] autoselect all movable units --- client/src/move_ui.rs | 16 ++++++++++++++-- server/src/player.rs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/client/src/move_ui.rs b/client/src/move_ui.rs index e6327192..a0b9851d 100644 --- a/client/src/move_ui.rs +++ b/client/src/move_ui.rs @@ -13,7 +13,7 @@ fn possible_destinations( game: &Game, start: Position, player_index: usize, - units: &Vec, + units: &[u32], ) -> Vec { if let Movement { movement_actions_left, @@ -61,7 +61,19 @@ pub fn click(pos: Position, s: &MoveSelection, mouse_pos: Vec2, game: &Game) -> let unit = unit_at_pos(pos, mouse_pos, p); unit.map_or(StateUpdate::None, |unit_id| { new.start = Some(pos); - unit_selection_clicked(unit_id, &mut new.units); + if new.units.is_empty() { + //select all possible units + new.units = p + .units + .iter() + .filter(|u| { + !possible_destinations(game, pos, new.player_index, &[u.id]).is_empty() + }) + .map(|u| u.id) + .collect(); + } else { + unit_selection_clicked(unit_id, &mut new.units); + } if new.units.is_empty() { new.destinations.clear(); new.start = None; diff --git a/server/src/player.rs b/server/src/player.rs index f2a499e6..59459c4c 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -748,7 +748,7 @@ impl Player { pub fn can_move_units( &self, game: &Game, - units: &Vec, + units: &[u32], starting: Position, destination: Position, movement_actions_left: u32, From 5859e89df116d7dd6e4e6f03f277fc59f1808e1e Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 15:33:03 +0100 Subject: [PATCH 2/7] autoselect all movable units --- client/src/assets.rs | 4 +- client/src/city_ui.rs | 26 ++++----- client/src/map_ui.rs | 116 ++++++++++++++++++++++++++++------------ client/src/move_ui.rs | 68 +++++++++++------------ client/src/player_ui.rs | 2 +- 5 files changed, 131 insertions(+), 85 deletions(-) diff --git a/client/src/assets.rs b/client/src/assets.rs index ee47492c..e6520912 100644 --- a/client/src/assets.rs +++ b/client/src/assets.rs @@ -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, @@ -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 diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index 87dfc5d9..da5364d4 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -12,7 +12,8 @@ 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::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}; @@ -45,24 +46,29 @@ impl CityMenu { } } -pub type IconActionVec<'a> = Vec<(&'a Texture2D, String, Box StateUpdate + 'a>)>; +pub type IconAction<'a> = (&'a Texture2D, String, Box StateUpdate + 'a>); + +pub type IconActionVec<'a> = Vec>; 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![]; + move_units_button(game, pos, &menu.player, &state).map(|i| icons.push(i)); icons.push(( &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, - menu.city_position, - possible_resource_collections(game, menu.city_position, menu.city_owner_index), + pos, + possible_resource_collections(game, pos, menu.city_owner_index), ))) }), )); @@ -135,17 +141,7 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) } } - 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 + show_map_action_buttons(state, &icons) } pub fn city_labels(game: &Game, city: &City) -> Vec { diff --git a/client/src/map_ui.rs b/client/src/map_ui.rs index f7d276c1..adc56f32 100644 --- a/client/src/map_ui.rs +++ b/client/src/map_ui.rs @@ -8,11 +8,12 @@ use server::game::{Game, GameState}; use server::map::Terrain; use server::playing_actions::PlayingAction; use server::position::Position; -use server::unit::MovementRestriction; +use server::unit::{MovementRestriction, Unit}; -use crate::city_ui::{draw_city, show_city_menu, CityMenu}; +use crate::city_ui::{draw_city, show_city_menu, CityMenu, IconAction, IconActionVec}; use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate}; use crate::layout_ui::{bottom_center_texture, icon_pos}; +use crate::move_ui::{movable_units, possible_destinations, MoveSelection}; use crate::{collect_ui, hex_ui, unit_ui}; fn terrain_font_color(t: &Terrain) -> Color { @@ -139,41 +140,88 @@ fn highlight_if(b: bool) -> f32 { } } -pub fn show_tile_menu( - game: &Game, - position: Position, - player: &ShownPlayer, - state: &State, +pub fn show_tile_menu<'a>( + game: &'a Game, + pos: Position, + player: &'a ShownPlayer, + state: &'a State, ) -> StateUpdate { - if let Some(c) = game.get_any_city(position) { - show_city_menu( - game, - &CityMenu::new(player, c.player_index, position), - state, - ) + if let Some(c) = game.get_any_city(pos) { + return show_city_menu(game, &CityMenu::new(player, c.player_index, pos), state); + }; + + let settlers: Vec = unit_ui::units_on_tile(game, pos) + .filter_map(|(_, unit)| { + if unit.can_found_city(game) { + Some(unit) + } else { + None + } + }) + .collect::>(); + + let mut icons: IconActionVec<'a> = vec![]; + move_units_button(game, pos, player, &state).map(|i| icons.push(i)); + found_city_button(state, settlers).map(|i| icons.push(i)); + + show_map_action_buttons(state, &icons) +} + +fn found_city_button<'a>(state: &'a State, settlers: Vec) -> Option> { + if settlers.is_empty() { + None } else { - let units = unit_ui::units_on_tile(game, position); - let settlers = units - .filter_map(|(_, unit)| { - if unit.can_found_city(game) { - Some(unit) - } else { - None - } - }) - .collect::>(); - if !settlers.is_empty() - && bottom_center_texture(state, &state.assets.settle, icon_pos(0, -1), "Settle") - { - let settler = settlers - .iter() - .find(|u| u.movement_restriction != MovementRestriction::None) - .unwrap_or(&settlers[0]); - StateUpdate::execute(Action::Playing(PlayingAction::FoundCity { - settler: settler.id, + Some(( + &state.assets.settle, + "Settle".to_string(), + Box::new(move || { + let settler = settlers + .iter() + .find(|u| u.movement_restriction != MovementRestriction::None) + .unwrap_or(&settlers[0]); + StateUpdate::execute(Action::Playing(PlayingAction::FoundCity { + settler: settler.id, + })) + }), + )) + } +} + +pub fn move_units_button<'a>( + game: &'a Game, + pos: Position, + player: &'a ShownPlayer, + state: &'a State, +) -> Option> { + if movable_units(pos, game, player.get(game)).is_empty() { + return None; + } + Some(( + &state.assets.move_units, + "Move units".to_string(), + Box::new(move || { + StateUpdate::execute(Action::Playing(PlayingAction::MoveUnits)) + let movable_units = movable_units(pos, game, player.get(game)); + StateUpdate::OpenDialog(ActiveDialog::MoveUnits(MoveSelection { + player_index: player.index, + start: Some(pos), + destinations: possible_destinations(game, pos, player.index, &movable_units), + units: movable_units, })) - } else { - StateUpdate::None + }), + )) +} + +pub fn show_map_action_buttons(state: &State, icons: &IconActionVec) -> StateUpdate { + 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 } diff --git a/client/src/move_ui.rs b/client/src/move_ui.rs index a0b9851d..70524da9 100644 --- a/client/src/move_ui.rs +++ b/client/src/move_ui.rs @@ -3,46 +3,48 @@ use macroquad::math::{u32, Vec2}; use server::action::Action; use server::game::Game; use server::game::GameState::Movement; +use server::player::Player; use server::position::Position; use server::unit::MovementAction; use crate::client_state::{ActiveDialog, StateUpdate}; use crate::unit_ui::{unit_at_pos, unit_selection_clicked}; -fn possible_destinations( +pub fn possible_destinations( game: &Game, start: Position, player_index: usize, units: &[u32], ) -> Vec { - if let Movement { + let player = game.get_player(player_index); + + let (moved_units, movement_actions_left) = if let Movement { movement_actions_left, moved_units, } = &game.state { - let player = game.get_player(player_index); - - game.map - .tiles - .keys() - .copied() - .filter(|dest| { - start != *dest - && player - .can_move_units( - game, - units, - start, - *dest, - *movement_actions_left, - moved_units, - ) - .is_ok() - }) - .collect::>() + (moved_units, movement_actions_left) } else { - vec![] - } + (&vec![], &1) + }; + + start + .neighbors() + .into_iter() + .filter(|dest| { + game.map.tiles.get(dest).is_some() + && player + .can_move_units( + game, + units, + start, + *dest, + *movement_actions_left, + moved_units, + ) + .is_ok() + }) + .collect::>() } pub fn click(pos: Position, s: &MoveSelection, mouse_pos: Vec2, game: &Game) -> StateUpdate { @@ -62,15 +64,7 @@ pub fn click(pos: Position, s: &MoveSelection, mouse_pos: Vec2, game: &Game) -> unit.map_or(StateUpdate::None, |unit_id| { new.start = Some(pos); if new.units.is_empty() { - //select all possible units - new.units = p - .units - .iter() - .filter(|u| { - !possible_destinations(game, pos, new.player_index, &[u.id]).is_empty() - }) - .map(|u| u.id) - .collect(); + new.units = movable_units(pos, game, p); } else { unit_selection_clicked(unit_id, &mut new.units); } @@ -85,6 +79,14 @@ pub fn click(pos: Position, s: &MoveSelection, mouse_pos: Vec2, game: &Game) -> } } +pub fn movable_units(pos: Position, game: &Game, p: &Player) -> Vec { + p.units + .iter() + .filter(|u| !possible_destinations(game, pos, p.index, &[u.id]).is_empty()) + .map(|u| u.id) + .collect() +} + #[derive(Clone, Debug)] pub struct MoveSelection { pub player_index: usize, diff --git a/client/src/player_ui.rs b/client/src/player_ui.rs index 6b4ebdc3..62363850 100644 --- a/client/src/player_ui.rs +++ b/client/src/player_ui.rs @@ -307,7 +307,7 @@ fn action_buttons( player: &ShownPlayer, assets: &Assets, ) -> StateUpdate { - if bottom_left_texture(state, &assets.movement, icon_pos(0, -3), "Move units") { + if bottom_left_texture(state, &assets.move_units, icon_pos(0, -3), "Move units") { return StateUpdate::execute(Action::Playing(PlayingAction::MoveUnits)); } if bottom_left_texture( From f9542c9e2c9ec5ce264289ad387b674dc5e213af Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 15:39:08 +0100 Subject: [PATCH 3/7] autoselect all movable units --- client/src/client_state.rs | 8 ++++---- client/src/map_ui.rs | 7 ------- client/src/move_ui.rs | 23 +++++++++++++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/client/src/client_state.rs b/client/src/client_state.rs index 1e5d8a29..edb8493d 100644 --- a/client/src/client_state.rs +++ b/client/src/client_state.rs @@ -16,7 +16,7 @@ use crate::combat_ui::RemoveCasualtiesSelection; use crate::construct_ui::ConstructionPayment; use crate::happiness_ui::IncreaseHappiness; use crate::layout_ui::FONT_SIZE; -use crate::move_ui::MoveSelection; +use crate::move_ui::{movable_units, possible_destinations, MoveSelection}; use crate::recruit_unit_ui::{RecruitAmount, RecruitSelection}; use crate::status_phase_ui::ChooseAdditionalAdvances; @@ -425,9 +425,9 @@ 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 } @@ -435,7 +435,7 @@ impl State { pub fn game_state_dialog(&self, game: &Game) -> ActiveDialog { match &game.state { GameState::Movement { .. } => { - ActiveDialog::MoveUnits(MoveSelection::new(game.active_player())) + ActiveDialog::MoveUnits(MoveSelection::new(game.active_player(), self.focused_tile, game)) } GameState::CulturalInfluenceResolution(c) => { ActiveDialog::CulturalInfluenceResolution(c.clone()) diff --git a/client/src/map_ui.rs b/client/src/map_ui.rs index adc56f32..0119e4d5 100644 --- a/client/src/map_ui.rs +++ b/client/src/map_ui.rs @@ -201,13 +201,6 @@ pub fn move_units_button<'a>( "Move units".to_string(), Box::new(move || { StateUpdate::execute(Action::Playing(PlayingAction::MoveUnits)) - let movable_units = movable_units(pos, game, player.get(game)); - StateUpdate::OpenDialog(ActiveDialog::MoveUnits(MoveSelection { - player_index: player.index, - start: Some(pos), - destinations: possible_destinations(game, pos, player.index, &movable_units), - units: movable_units, - })) }), )) } diff --git a/client/src/move_ui.rs b/client/src/move_ui.rs index 70524da9..c6b20e5c 100644 --- a/client/src/move_ui.rs +++ b/client/src/move_ui.rs @@ -96,12 +96,23 @@ pub struct MoveSelection { } impl MoveSelection { - pub fn new(player_index: usize) -> MoveSelection { - MoveSelection { - player_index, - units: vec![], - start: None, - destinations: vec![], + pub fn new(player_index: usize, start: Option, game: &Game) -> MoveSelection { + match start { + Some(pos) => { + let movable_units = movable_units(pos, game, game.get_player(player_index)); + MoveSelection { + player_index, + start: Some(pos), + destinations: possible_destinations(game, pos, player_index, &movable_units), + units: movable_units, + } + } + None => MoveSelection { + player_index, + start: None, + units: vec![], + destinations: vec![], + }, } } } From 0431f27da41b80df38eb016f88d5859e36acb420 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 15:39:57 +0100 Subject: [PATCH 4/7] autoselect all movable units --- client/src/city_ui.rs | 6 ++++-- client/src/client_state.rs | 10 ++++++---- client/src/map_ui.rs | 16 +++++++++------- client/src/move_ui.rs | 2 +- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index da5364d4..076ba4bd 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -48,7 +48,7 @@ impl CityMenu { pub type IconAction<'a> = (&'a Texture2D, String, Box StateUpdate + 'a>); -pub type IconActionVec<'a> = Vec>; +pub type IconActionVec<'a> = Vec>; pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> StateUpdate { let city = menu.get_city(game); @@ -59,7 +59,9 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) return StateUpdate::None; } let mut icons: IconActionVec<'a> = vec![]; - move_units_button(game, pos, &menu.player, &state).map(|i| icons.push(i)); + if let Some(i) = move_units_button(game, pos, &menu.player, state) { + icons.push(i) + } icons.push(( &state.assets.resources[&ResourceType::Food], "Collect Resources".to_string(), diff --git a/client/src/client_state.rs b/client/src/client_state.rs index edb8493d..c1badd1d 100644 --- a/client/src/client_state.rs +++ b/client/src/client_state.rs @@ -16,7 +16,7 @@ use crate::combat_ui::RemoveCasualtiesSelection; use crate::construct_ui::ConstructionPayment; use crate::happiness_ui::IncreaseHappiness; use crate::layout_ui::FONT_SIZE; -use crate::move_ui::{movable_units, possible_destinations, MoveSelection}; +use crate::move_ui::MoveSelection; use crate::recruit_unit_ui::{RecruitAmount, RecruitSelection}; use crate::status_phase_ui::ChooseAdditionalAdvances; @@ -434,9 +434,11 @@ impl State { #[must_use] pub fn game_state_dialog(&self, game: &Game) -> ActiveDialog { match &game.state { - GameState::Movement { .. } => { - ActiveDialog::MoveUnits(MoveSelection::new(game.active_player(), self.focused_tile, game)) - } + GameState::Movement { .. } => ActiveDialog::MoveUnits(MoveSelection::new( + game.active_player(), + self.focused_tile, + game, + )), GameState::CulturalInfluenceResolution(c) => { ActiveDialog::CulturalInfluenceResolution(c.clone()) } diff --git a/client/src/map_ui.rs b/client/src/map_ui.rs index 0119e4d5..db0c691a 100644 --- a/client/src/map_ui.rs +++ b/client/src/map_ui.rs @@ -13,7 +13,7 @@ use server::unit::{MovementRestriction, Unit}; use crate::city_ui::{draw_city, show_city_menu, CityMenu, IconAction, IconActionVec}; use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate}; use crate::layout_ui::{bottom_center_texture, icon_pos}; -use crate::move_ui::{movable_units, possible_destinations, MoveSelection}; +use crate::move_ui::movable_units; use crate::{collect_ui, hex_ui, unit_ui}; fn terrain_font_color(t: &Terrain) -> Color { @@ -161,13 +161,17 @@ pub fn show_tile_menu<'a>( .collect::>(); let mut icons: IconActionVec<'a> = vec![]; - move_units_button(game, pos, player, &state).map(|i| icons.push(i)); - found_city_button(state, settlers).map(|i| icons.push(i)); + if let Some(i) = move_units_button(game, pos, player, state) { + icons.push(i) + } + if let Some(i) = found_city_button(state, settlers) { + icons.push(i) + } show_map_action_buttons(state, &icons) } -fn found_city_button<'a>(state: &'a State, settlers: Vec) -> Option> { +fn found_city_button(state: &State, settlers: Vec) -> Option> { if settlers.is_empty() { None } else { @@ -199,9 +203,7 @@ pub fn move_units_button<'a>( Some(( &state.assets.move_units, "Move units".to_string(), - Box::new(move || { - StateUpdate::execute(Action::Playing(PlayingAction::MoveUnits)) - }), + Box::new(move || StateUpdate::execute(Action::Playing(PlayingAction::MoveUnits))), )) } diff --git a/client/src/move_ui.rs b/client/src/move_ui.rs index c6b20e5c..47afeafe 100644 --- a/client/src/move_ui.rs +++ b/client/src/move_ui.rs @@ -32,7 +32,7 @@ pub fn possible_destinations( .neighbors() .into_iter() .filter(|dest| { - game.map.tiles.get(dest).is_some() + game.map.tiles.contains_key(dest) && player .can_move_units( game, From 5b5aa46c367e55400f2219a64c7beb8d2163f188 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 16:35:48 +0100 Subject: [PATCH 5/7] autoselect all movable units --- client/src/city_ui.rs | 189 ++++++++++++++++++++++--------------- client/src/client_state.rs | 2 +- client/src/construct_ui.rs | 10 +- client/src/influence_ui.rs | 4 +- client/src/map_ui.rs | 19 ++-- 5 files changed, 135 insertions(+), 89 deletions(-) diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index 076ba4bd..e8238fd8 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -10,7 +10,7 @@ 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::construct_ui::{new_building_positions, ConstructionPayment, ConstructionProject}; use crate::hex_ui::Point; use crate::layout_ui::draw_scaled_icon; use crate::map_ui::{move_units_button, show_map_action_buttons}; @@ -58,74 +58,38 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) if !can_play { return StateUpdate::None; } - let mut icons: IconActionVec<'a> = vec![]; - if let Some(i) = move_units_button(game, pos, &menu.player, state) { - icons.push(i) - } - icons.push(( - &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), - ))) - }), - )); - 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 icons: IconActionVec<'a> = vec![ + 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![icons, buildings, wonders] + .into_iter() + .flatten() + .collect(), + ) +} + +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 x: IconAction<'a> = ( &state.assets.wonders[&w.name], format!("Build wonder {}", w.name), Box::new(move || { @@ -139,11 +103,88 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) ), )) }), - )); - } - } + ); + x + }) + .collect() +} + +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 x: 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), + ), + )) + }), + ); + x + }) + .collect() +} - show_map_action_buttons(state, &icons) +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 { @@ -164,11 +205,11 @@ pub fn city_labels(game: &Game, city: &City) -> Vec { .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() ) } @@ -242,7 +283,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, @@ -269,10 +310,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() } diff --git a/client/src/client_state.rs b/client/src/client_state.rs index c1badd1d..893d7b1c 100644 --- a/client/src/client_state.rs +++ b/client/src/client_state.rs @@ -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()] diff --git a/client/src/construct_ui.rs b/client/src/construct_ui.rs index ea74a55e..9d5a9c5d 100644 --- a/client/src/construct_ui.rs +++ b/client/src/construct_ui.rs @@ -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> { +pub fn new_building_positions( + building: Building, + city: &City, + map: &Map, +) -> Vec<(Building, Option)> { 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 } diff --git a/client/src/influence_ui.rs b/client/src/influence_ui.rs index 58d428d8..c790e403 100644 --- a/client/src/influence_ui.rs +++ b/client/src/influence_ui.rs @@ -30,7 +30,7 @@ pub fn cultural_influence_resolution_dialog( r: &CulturalInfluenceResolution, player: &ShownPlayer, ) -> StateUpdate { - let name = building_name(&r.city_piece); + let name = building_name(r.city_piece); let pile = ResourcePile::culture_tokens(r.roll_boost_cost); show_resource_pile(state, player, &pile); if ok_button( @@ -96,7 +96,7 @@ fn show_city( *b, ) { if player.resources.can_afford(&cost) { - let name = building_name(b); + let name = building_name(*b); state.set_world_camera(); draw_circle_lines(center.x, center.y, BUILDING_SIZE, 1., WHITE); show_tooltip_for_circle( diff --git a/client/src/map_ui.rs b/client/src/map_ui.rs index db0c691a..243edb5f 100644 --- a/client/src/map_ui.rs +++ b/client/src/map_ui.rs @@ -160,15 +160,16 @@ pub fn show_tile_menu<'a>( }) .collect::>(); - let mut icons: IconActionVec<'a> = vec![]; - if let Some(i) = move_units_button(game, pos, player, state) { - icons.push(i) - } - if let Some(i) = found_city_button(state, settlers) { - icons.push(i) - } - - show_map_action_buttons(state, &icons) + show_map_action_buttons( + state, + &vec![ + move_units_button(game, pos, player, state), + found_city_button(state, settlers), + ] + .into_iter() + .flatten() + .collect(), + ) } fn found_city_button(state: &State, settlers: Vec) -> Option> { From 72bf532e00f1bff5bff88e2f8e31617f3eaa4cca Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 16:37:03 +0100 Subject: [PATCH 6/7] autoselect all movable units --- client/src/city_ui.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index e8238fd8..6319fe55 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -89,7 +89,7 @@ fn wonder_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> Ico .iter() .filter(|w| city.can_build_wonder(w, owner, game)) .map(|w| { - let x: IconAction<'a> = ( + let a: IconAction<'a> = ( &state.assets.wonders[&w.name], format!("Build wonder {}", w.name), Box::new(move || { @@ -104,7 +104,7 @@ fn wonder_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> Ico )) }), ); - x + a }) .collect() } @@ -131,7 +131,7 @@ fn building_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> I pos.map_or(String::new(), |p| format!(" at {p}")), owner.construct_cost(b, city), ); - let x: IconAction<'a> = ( + let a: IconAction<'a> = ( &state.assets.buildings[&b], tooltip, Box::new(move || { @@ -146,7 +146,7 @@ fn building_icons<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> I )) }), ); - x + a }) .collect() } From 136e24318316c5ac4717483f83a2f918714123d2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 5 Jan 2025 20:14:15 +0100 Subject: [PATCH 7/7] happiness button in city --- client/src/city_ui.rs | 50 +++++++++++++++++++++++++++++--------- client/src/happiness_ui.rs | 31 ++++++++++------------- client/src/player_ui.rs | 6 +++-- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index 6319fe55..10948a14 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -1,22 +1,21 @@ -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::{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 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 std::ops::Add; pub struct CityMenu { pub player: ShownPlayer, @@ -58,7 +57,9 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) if !can_play { return StateUpdate::None; } - let icons: IconActionVec<'a> = vec![ + + 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)), @@ -73,13 +74,38 @@ pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) show_map_action_buttons( state, - &vec![icons, buildings, wonders] + &vec![base_icons, buildings, wonders] .into_iter() .flatten() .collect(), ) } +fn increase_happiness_button<'a>( + game: &'a Game, + menu: &'a CityMenu, + state: &'a State, +) -> Option> { + 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); diff --git a/client/src/happiness_ui.rs b/client/src/happiness_ui.rs index d34dec6f..4923d52e 100644 --- a/client/src/happiness_ui.rs +++ b/client/src/happiness_ui.rs @@ -1,6 +1,7 @@ use server::action::Action; use server::city::City; use server::game::Game; +use server::player::Player; use server::playing_actions::PlayingAction; use server::position::Position; use server::resource_pile::ResourcePile; @@ -16,8 +17,12 @@ pub struct IncreaseHappiness { } impl IncreaseHappiness { - pub fn new(steps: Vec<(Position, u32)>, cost: ResourcePile) -> IncreaseHappiness { - IncreaseHappiness { steps, cost } + pub fn new(p: &Player) -> IncreaseHappiness { + let steps = p.cities.iter().map(|c| (c.position, 0)).collect(); + IncreaseHappiness { + steps, + cost: ResourcePile::empty(), + } } } @@ -29,7 +34,7 @@ pub fn increase_happiness_click( ) -> StateUpdate { if let Some(city) = player.get(game).get_city(pos) { StateUpdate::OpenDialog(ActiveDialog::IncreaseHappiness(add_increase_happiness( - city, pos, h, + city, h, ))) } else { StateUpdate::None @@ -38,7 +43,6 @@ pub fn increase_happiness_click( pub fn add_increase_happiness( city: &City, - pos: Position, increase_happiness: &IncreaseHappiness, ) -> IncreaseHappiness { let mut total_cost = increase_happiness.cost.clone(); @@ -47,7 +51,7 @@ pub fn add_increase_happiness( .iter() .map(|(p, steps)| { let old_steps = *steps; - if *p == pos { + if *p == city.position { if let Some(r) = increase_happiness_steps(city, &total_cost, old_steps) { total_cost = r.1; return (*p, r.0); @@ -57,7 +61,10 @@ pub fn add_increase_happiness( }) .collect(); - IncreaseHappiness::new(new_steps, total_cost) + IncreaseHappiness { + steps: new_steps, + cost: total_cost, + } } fn increase_happiness_steps( @@ -116,15 +123,3 @@ pub fn increase_happiness_menu( } StateUpdate::None } - -pub fn start_increase_happiness(game: &Game, player: &ShownPlayer) -> StateUpdate { - StateUpdate::OpenDialog(ActiveDialog::IncreaseHappiness(IncreaseHappiness::new( - player - .get(game) - .cities - .iter() - .map(|c| (c.position, 0)) - .collect(), - ResourcePile::empty(), - ))) -} diff --git a/client/src/player_ui.rs b/client/src/player_ui.rs index 62363850..61ec6e68 100644 --- a/client/src/player_ui.rs +++ b/client/src/player_ui.rs @@ -2,7 +2,7 @@ use crate::assets::Assets; use crate::city_ui::city_labels; use crate::client::Features; use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate}; -use crate::happiness_ui::start_increase_happiness; +use crate::happiness_ui::IncreaseHappiness; use crate::layout_ui::{ bottom_center_texture, bottom_left_texture, bottom_right_texture, icon_pos, left_mouse_button_pressed_in_rect, top_center_texture, ICON_SIZE, @@ -324,7 +324,9 @@ fn action_buttons( icon_pos(0, -2), "Increase happiness", ) { - return start_increase_happiness(game, player); + return StateUpdate::OpenDialog(ActiveDialog::IncreaseHappiness(IncreaseHappiness::new( + player.get(game), + ))); } if bottom_left_texture( state,