diff --git a/client/assets/academy-cap-svgrepo-com.png b/client/assets/academy-cap-svgrepo-com.png new file mode 100644 index 00000000..5b2907db Binary files /dev/null and b/client/assets/academy-cap-svgrepo-com.png differ diff --git a/client/assets/cancel-svgrepo-com.png b/client/assets/cancel-svgrepo-com.png new file mode 100644 index 00000000..9766159b Binary files /dev/null and b/client/assets/cancel-svgrepo-com.png differ diff --git a/client/assets/castle-fortress-14-svgrepo-com.png b/client/assets/castle-fortress-14-svgrepo-com.png new file mode 100644 index 00000000..cc49d18e Binary files /dev/null and b/client/assets/castle-fortress-14-svgrepo-com.png differ diff --git a/client/assets/castle-manor-14-svgrepo-com.png b/client/assets/castle-manor-14-svgrepo-com.png new file mode 100644 index 00000000..76ff896d Binary files /dev/null and b/client/assets/castle-manor-14-svgrepo-com.png differ diff --git a/client/assets/cross-svgrepo-com.png b/client/assets/cross-svgrepo-com.png new file mode 100644 index 00000000..9589cbf3 Binary files /dev/null and b/client/assets/cross-svgrepo-com.png differ diff --git a/client/assets/elephant-svgrepo-com.png b/client/assets/elephant-svgrepo-com.png new file mode 100644 index 00000000..260e2f68 Binary files /dev/null and b/client/assets/elephant-svgrepo-com.png differ diff --git a/client/assets/flag-svgrepo-com.png b/client/assets/flag-svgrepo-com.png new file mode 100644 index 00000000..b6dc8350 Binary files /dev/null and b/client/assets/flag-svgrepo-com.png differ diff --git a/client/assets/horse-head-svgrepo-com.png b/client/assets/horse-head-svgrepo-com.png new file mode 100644 index 00000000..05e18b12 Binary files /dev/null and b/client/assets/horse-head-svgrepo-com.png differ diff --git a/client/assets/in-progress-svgrepo-com.png b/client/assets/in-progress-svgrepo-com.png new file mode 100644 index 00000000..8fb20f44 Binary files /dev/null and b/client/assets/in-progress-svgrepo-com.png differ diff --git a/client/assets/market-place-svgrepo-com.png b/client/assets/market-place-svgrepo-com.png new file mode 100644 index 00000000..e80d9c84 Binary files /dev/null and b/client/assets/market-place-svgrepo-com.png differ diff --git a/client/assets/minus-circle-svgrepo-com.png b/client/assets/minus-circle-svgrepo-com.png new file mode 100644 index 00000000..57d8ac61 Binary files /dev/null and b/client/assets/minus-circle-svgrepo-com.png differ diff --git a/client/assets/neutral-face-svgrepo-com.png b/client/assets/neutral-face-svgrepo-com.png deleted file mode 100644 index 5097a7c7..00000000 Binary files a/client/assets/neutral-face-svgrepo-com.png and /dev/null differ diff --git a/client/assets/obelisk-svgrepo-com.png b/client/assets/obelisk-svgrepo-com.png new file mode 100644 index 00000000..e2e93e14 Binary files /dev/null and b/client/assets/obelisk-svgrepo-com.png differ diff --git a/client/assets/observatory-exploration-svgrepo-com.png b/client/assets/observatory-exploration-svgrepo-com.png new file mode 100644 index 00000000..e3425aea Binary files /dev/null and b/client/assets/observatory-exploration-svgrepo-com.png differ diff --git a/client/assets/ok-circle-svgrepo-com.png b/client/assets/ok-circle-svgrepo-com.png new file mode 100644 index 00000000..7bfd2de6 Binary files /dev/null and b/client/assets/ok-circle-svgrepo-com.png differ diff --git a/client/assets/plus-circle-svgrepo-com.png b/client/assets/plus-circle-svgrepo-com.png new file mode 100644 index 00000000..5487673d Binary files /dev/null and b/client/assets/plus-circle-svgrepo-com.png differ diff --git a/client/assets/port-location-svgrepo-com.png b/client/assets/port-location-svgrepo-com.png new file mode 100644 index 00000000..2c7ad03f Binary files /dev/null and b/client/assets/port-location-svgrepo-com.png differ diff --git a/client/assets/pyramid-svgrepo-com.png b/client/assets/pyramid-svgrepo-com.png new file mode 100644 index 00000000..8992d402 Binary files /dev/null and b/client/assets/pyramid-svgrepo-com.png differ diff --git a/client/assets/ship-svgrepo-com.png b/client/assets/ship-svgrepo-com.png new file mode 100644 index 00000000..ccadd5b6 Binary files /dev/null and b/client/assets/ship-svgrepo-com.png differ diff --git a/client/assets/temple-building-with-columns-svgrepo-com.png b/client/assets/temple-building-with-columns-svgrepo-com.png new file mode 100644 index 00000000..6bbfd0c5 Binary files /dev/null and b/client/assets/temple-building-with-columns-svgrepo-com.png differ diff --git a/client/assets/wagon-svgrepo-com.png b/client/assets/wagon-svgrepo-com.png new file mode 100644 index 00000000..38c45151 Binary files /dev/null and b/client/assets/wagon-svgrepo-com.png differ diff --git a/client/assets/warrior-svgrepo-com.png b/client/assets/warrior-svgrepo-com.png new file mode 100644 index 00000000..ba2b43b3 Binary files /dev/null and b/client/assets/warrior-svgrepo-com.png differ diff --git a/client/src/advance_ui.rs b/client/src/advance_ui.rs index c35d62a1..f2b62477 100644 --- a/client/src/advance_ui.rs +++ b/client/src/advance_ui.rs @@ -21,7 +21,7 @@ use std::collections::HashMap; #[derive(Clone)] pub struct AdvancePayment { - name: String, + pub name: String, payment: Payment, cost: u32, } diff --git a/client/src/assets.rs b/client/src/assets.rs index 688fd29b..0b5f9b5a 100644 --- a/client/src/assets.rs +++ b/client/src/assets.rs @@ -5,31 +5,39 @@ use macroquad::prelude::{ }; use macroquad::texture::Texture2D; use macroquad::ui::{root_ui, Skin}; +use server::city_pieces::Building; use server::map::Terrain; use server::unit::UnitType; use std::collections::HashMap; pub struct Assets { pub terrain: HashMap, + pub exhausted: Texture2D, pub units: HashMap, pub skin: Skin, pub font: Font, // mood icons pub angry: Texture2D, - pub neutral: Texture2D, - pub happy: Texture2D, // action icons pub movement: Texture2D, pub log: Texture2D, pub end_turn: Texture2D, pub advances: Texture2D, + pub settle: Texture2D, + + // UI pub redo: Texture2D, pub reset: Texture2D, pub undo: Texture2D, + pub plus: Texture2D, + pub minus: Texture2D, + pub ok_blocked: Texture2D, + pub ok: Texture2D, + pub cancel: Texture2D, + pub restore_menu: Texture2D, - // UI pub zoom_in: Texture2D, pub zoom_out: Texture2D, pub up: Texture2D, @@ -38,73 +46,49 @@ pub struct Assets { pub right: Texture2D, pub victory_points: Texture2D, pub active_player: Texture2D, - pub restore_menu: Texture2D, // Admin pub import: Texture2D, pub export: Texture2D, - // pub cities: HashMap, pub resources: HashMap, + pub buildings: HashMap, + pub wonders: HashMap, } impl Assets { pub async fn new(features: &Features) -> Self { - let happy = load_png(include_bytes!("../assets/happy-emoji-svgrepo-com.png")); let font_name = features.get_asset("HTOWERT.TTF"); Self { font: load_ttf_font(&font_name).await.unwrap(), // can't share font - causes panic terrain: Self::terrain(features).await, - units: HashMap::new(), + exhausted: load_png(include_bytes!("../assets/cross-svgrepo-com.png")), + units: Self::units(), skin: Self::skin(&load_ttf_font(&font_name).await.unwrap()), - // mood icons angry: load_png(include_bytes!("../assets/angry-face-svgrepo-com.png")), - neutral: load_png(include_bytes!("../assets/neutral-face-svgrepo-com.png")), - happy: happy.clone(), - - // resource icons - resources: [ - ( - ResourceType::Food, - load_png(include_bytes!("../assets/wheat-grain-svgrepo-com.png")), - ), - ( - ResourceType::Wood, - load_png(include_bytes!("../assets/wood-nature-svgrepo-com.png")), - ), - ( - ResourceType::Ore, - load_png(include_bytes!("../assets/rock-svgrepo-com.png")), - ), - ( - ResourceType::Ideas, - load_png(include_bytes!("../assets/light-bulb-idea-svgrepo-com.png")), - ), - ( - ResourceType::Gold, - load_png(include_bytes!("../assets/gold-ingots-gold-svgrepo-com.png")), - ), - (ResourceType::MoodTokens, happy.clone()), - ( - ResourceType::CultureTokens, - load_png(include_bytes!("../assets/theater-drama-svgrepo-com.png")), - ), - ] - .iter() - .cloned() - .collect(), + resources: Self::resources(), + buildings: Self::buildings(), + wonders: Self::wonders(), // action icons 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")), + settle: load_png(include_bytes!("../assets/castle-manor-14-svgrepo-com.png")), + + // UI redo: load_png(include_bytes!("../assets/redo-svgrepo-com.png")), reset: load_png(include_bytes!("../assets/reset-svgrepo-com.png")), undo: load_png(include_bytes!("../assets/undo-svgrepo-com.png")), + plus: load_png(include_bytes!("../assets/plus-circle-svgrepo-com.png")), + minus: load_png(include_bytes!("../assets/minus-circle-svgrepo-com.png")), + ok: load_png(include_bytes!("../assets/ok-circle-svgrepo-com.png")), + ok_blocked: load_png(include_bytes!("../assets/in-progress-svgrepo-com.png")), + cancel: load_png(include_bytes!("../assets/cancel-svgrepo-com.png")), + restore_menu: load_png(include_bytes!("../assets/restore-svgrepo-com.png")), - // UI zoom_in: load_png(include_bytes!("../assets/zoom-in-1462-svgrepo-com.png")), zoom_out: load_png(include_bytes!("../assets/zoom-out-1460-svgrepo-com.png")), up: load_png(include_bytes!("../assets/up-arrow-circle-svgrepo-com.png")), @@ -119,15 +103,133 @@ impl Assets { )), victory_points: load_png(include_bytes!("../assets/trophy-cup-svgrepo-com.png")), active_player: load_png(include_bytes!("../assets/triangle-svgrepo-com.png")), - restore_menu: load_png(include_bytes!("../assets/restore-svgrepo-com.png")), // Admin import: load_png(include_bytes!("../assets/import-3-svgrepo-com.png")), export: load_png(include_bytes!("../assets/export-2-svgrepo-com.png")), - // cities: HashMap::new(), } } + fn wonders() -> HashMap { + [( + "Pyramids".to_string(), + load_png(include_bytes!("../assets/pyramid-svgrepo-com.png")), + )] + .iter() + .cloned() + .collect() + } + + fn units() -> HashMap { + [ + ( + UnitType::Infantry, + load_png(include_bytes!("../assets/warrior-svgrepo-com.png")), + ), + ( + UnitType::Settler, + load_png(include_bytes!("../assets/wagon-svgrepo-com.png")), + ), + ( + UnitType::Cavalry, + load_png(include_bytes!("../assets/horse-head-svgrepo-com.png")), + ), + ( + UnitType::Elephant, + load_png(include_bytes!("../assets/elephant-svgrepo-com.png")), + ), + ( + UnitType::Ship, + load_png(include_bytes!("../assets/ship-svgrepo-com.png")), + ), + ( + UnitType::Leader, + load_png(include_bytes!("../assets/flag-svgrepo-com.png")), + ), + ] + .iter() + .cloned() + .collect() + } + + fn resources() -> HashMap { + [ + ( + ResourceType::Food, + load_png(include_bytes!("../assets/wheat-grain-svgrepo-com.png")), + ), + ( + ResourceType::Wood, + load_png(include_bytes!("../assets/wood-nature-svgrepo-com.png")), + ), + ( + ResourceType::Ore, + load_png(include_bytes!("../assets/rock-svgrepo-com.png")), + ), + ( + ResourceType::Ideas, + load_png(include_bytes!("../assets/light-bulb-idea-svgrepo-com.png")), + ), + ( + ResourceType::Gold, + load_png(include_bytes!("../assets/gold-ingots-gold-svgrepo-com.png")), + ), + ( + ResourceType::MoodTokens, + load_png(include_bytes!("../assets/happy-emoji-svgrepo-com.png")), + ), + ( + ResourceType::CultureTokens, + load_png(include_bytes!("../assets/theater-drama-svgrepo-com.png")), + ), + ] + .iter() + .cloned() + .collect() + } + + fn buildings() -> HashMap { + [ + ( + Building::Academy, + load_png(include_bytes!("../assets/academy-cap-svgrepo-com.png")), + ), + ( + Building::Market, + load_png(include_bytes!("../assets/market-place-svgrepo-com.png")), + ), + ( + Building::Obelisk, + load_png(include_bytes!("../assets/obelisk-svgrepo-com.png")), + ), + ( + Building::Observatory, + load_png(include_bytes!( + "../assets/observatory-exploration-svgrepo-com.png" + )), + ), + ( + Building::Fortress, + load_png(include_bytes!( + "../assets/castle-fortress-14-svgrepo-com.png" + )), + ), + ( + Building::Port, + load_png(include_bytes!("../assets/port-location-svgrepo-com.png")), + ), + ( + Building::Temple, + load_png(include_bytes!( + "../assets/temple-building-with-columns-svgrepo-com.png" + )), + ), + ] + .iter() + .cloned() + .collect() + } + async fn terrain(features: &Features) -> HashMap { let mut map: HashMap = HashMap::new(); diff --git a/client/src/cards_ui.rs b/client/src/cards_ui.rs new file mode 100644 index 00000000..8f467c49 --- /dev/null +++ b/client/src/cards_ui.rs @@ -0,0 +1,19 @@ +// pub fn show_wonders(game: &Game, player: &ShownPlayer, ui: &mut Ui) { +//todo move to cards ui +// let player = game.get_player(player.index); +// let y = 5.; +// +// for (i, card) in player.wonder_cards.iter().enumerate() { +// let req = match card.required_advances[..] { +// [] => String::from("no advances"), +// _ => card.required_advances.join(", "), +// }; +// ui.label( +// vec2(900. + i as f32 * 30.0, y), +// &format!( +// "Wonder Card {} cost {} requires {}", +// &card.name, card.cost, req +// ), +// ); +// } +// } diff --git a/client/src/city_ui.rs b/client/src/city_ui.rs index 905d7484..3987a2cc 100644 --- a/client/src/city_ui.rs +++ b/client/src/city_ui.rs @@ -1,20 +1,20 @@ use macroquad::prelude::*; -use macroquad::ui::Ui; 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::Units; +use server::unit::{UnitType, Units}; -use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate, StateUpdates}; +use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate}; use crate::collect_ui::{possible_resource_collections, CollectResources}; -use crate::construct_ui::{add_construct_button, add_wonder_buttons}; -use crate::hex_ui::draw_hex_center_text; -use crate::map_ui::show_generic_tile_menu; +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::{hex_ui, influence_ui, player_ui}; +use crate::resource_ui::ResourceType; +use crate::{hex_ui, player_ui}; pub struct CityMenu { pub player: ShownPlayer, @@ -44,52 +44,113 @@ impl CityMenu { } } -pub fn show_city_menu(game: &Game, menu: &CityMenu) -> StateUpdate { - let position = menu.city_position; +pub type IconActionVec<'a> = Vec<(&'a Texture2D, String, Box StateUpdate + 'a>)>; + +pub fn show_city_menu<'a>(game: &'a Game, menu: &'a CityMenu, state: &'a State) -> StateUpdate { let city = menu.get_city(game); - show_generic_tile_menu(game, position, &menu.player, city_label(game, city), |ui| { - let can_play = menu.player.can_play_action && menu.is_city_owner() && city.can_activate(); - if can_play { - if ui.button(None, "Collect Resources") { - return StateUpdate::SetDialog(ActiveDialog::CollectResources( - CollectResources::new( - menu.player.index, - menu.city_position, - possible_resource_collections( - game, - menu.city_position, - menu.city_owner_index, - ), - ), - )); - } - if ui.button(None, "Recruit Units") { - return RecruitAmount::new_selection( - game, - menu.player.index, - menu.city_position, - Units::empty(), - None, - &[], + 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::SetDialog(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 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::SetDialog(ActiveDialog::ConstructionPayment( + ConstructionPayment::new( + game, + name, + menu.player.index, + menu.city_position, + ConstructionProject::Building(building, pos), + ), + )) + }), + )); } } + } - let mut updates = StateUpdates::new(); - updates.add(add_building_actions(game, menu, ui)); + for w in &owner.wonder_cards { + if city.can_build_wonder(w, owner, game) { + icons.push(( + &state.assets.wonders[&w.name], + format!("Build wonder {}", w.name), + Box::new(move || { + StateUpdate::SetDialog(ActiveDialog::ConstructionPayment( + ConstructionPayment::new( + game, + &w.name, + menu.player.index, + menu.city_position, + ConstructionProject::Wonder(w.name.clone()), + ), + )) + }), + )); + } + } - if can_play { - updates.add(add_wonder_buttons(game, menu, ui)); + 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(); } - updates.result() - }) + } + StateUpdate::None } -fn city_label(game: &Game, city: &City) -> Vec { - vec![ - format!( - "size: {} mood: {} activated: {}", +pub fn city_labels(game: &Game, city: &City) -> Vec { + [ + vec![format!( + "Size: {} Mood: {} Activated: {}", city.size(), match city.mood_state { MoodState::Happy => "Happy", @@ -97,51 +158,29 @@ fn city_label(game: &Game, city: &City) -> Vec { MoodState::Angry => "Angry", }, city.is_activated() - ), - format!( - "Buildings: {}", - city.pieces - .building_owners() - .iter() - .filter_map(|(b, o)| { - o.as_ref().map(|o| { - if city.player_index == *o { - building_name(b).to_string() - } else { - format!( - "{} (owned by {})", - building_name(b), - game.get_player(*o).get_name() - ) - } - }) + )], + city.pieces + .building_owners() + .iter() + .filter_map(|(b, o)| { + o.as_ref().map(|o| { + if city.player_index == *o { + building_name(b).to_string() + } else { + format!( + "{} (owned by {})", + building_name(b), + game.get_player(*o).get_name() + ) + } }) - .collect::>() - .join(", ") - ), + }) + .collect(), ] + .concat() } -fn add_building_actions(game: &Game, menu: &CityMenu, ui: &mut Ui) -> StateUpdate { - if !menu.player.can_play_action { - return StateUpdate::None; - } - let closest_city_pos = influence_ui::closest_city(game, menu); - - let mut updates = StateUpdates::new(); - for (building, name) in building_names() { - updates.add(add_construct_button(game, menu, ui, &building, name)); - updates.add(influence_ui::add_influence_button( - game, - menu, - ui, - closest_city_pos, - &building, - name, - )); - } - updates.result() -} +pub const BUILDING_SIZE: f32 = 12.0; pub fn draw_city(owner: &Player, city: &City, state: &State) { let c = hex_ui::center(city.position); @@ -157,12 +196,22 @@ pub fn draw_city(owner: &Player, city: &City, state: &State) { .iter() .find(|(p, _)| p == &city.position) .map_or(String::new(), |(_, s)| format!("{s}")); - draw_hex_center_text(city.position, &steps); + state.draw_text(&steps, c.x - 5., c.y + 6.); } else { - match city.mood_state { - MoodState::Happy => draw_hex_center_text(city.position, "+"), - MoodState::Neutral => {} - MoodState::Angry => draw_hex_center_text(city.position, "-"), + let t = match city.mood_state { + MoodState::Happy => Some(&state.assets.resources[&ResourceType::MoodTokens]), + MoodState::Neutral => None, + MoodState::Angry => Some(&state.assets.angry), + }; + if let Some(t) = t { + let size = 15.; + draw_scaled_icon( + state, + t, + &format!("Happiness: {:?}", city.mood_state), + c.to_vec2() + vec2(-size / 2., -size / 2.), + size, + ); } } @@ -170,38 +219,53 @@ pub fn draw_city(owner: &Player, city: &City, state: &State) { city.pieces.wonders.iter().for_each(|w| { let p = hex_ui::rotate_around(c, 20.0, 90 * i); draw_circle(p.x, p.y, 18.0, player_ui::player_color(owner.index)); - draw_text(&w.name, p.x - 10.0, p.y + 10.0, 40.0, BLACK); + let size = 20.; + draw_scaled_icon( + state, + &state.assets.wonders[&w.name], + &w.name, + p.to_vec2() + vec2(-size / 2., -size / 2.), + size, + ); i += 1; }); for player_index in 0..4 { for b in &city.pieces.buildings(Some(player_index)) { - let p = if matches!(b, Building::Port) { - let r: f32 = city - .position - .coordinate() - .directions_to(city.port_position.unwrap().coordinate())[0] - .to_radians_pointy(); - hex_ui::rotate_around_rad(c, 60.0, r * -1.0 + std::f32::consts::PI / 3.0) + let p = building_position(city, c, i, *b); + draw_circle( + p.x, + p.y, + BUILDING_SIZE, + player_ui::player_color(player_index), + ); + let tooltip = if matches!(state.active_dialog, ActiveDialog::CulturalInfluence) { + "" } else { - hex_ui::rotate_around(c, 20.0, 90 * i) + building_name(b) }; - draw_circle(p.x, p.y, 12.0, player_ui::player_color(player_index)); - draw_text(building_symbol(b), p.x - 7.0, p.y + 8.0, 30.0, BLACK); + draw_scaled_icon( + state, + &state.assets.buildings[b], + tooltip, + p.to_vec2() + vec2(-8., -8.), + 16., + ); i += 1; } } } -fn building_symbol(b: &Building) -> &str { - match b { - Building::Academy => "A", - Building::Market => "M", - Building::Obelisk => "K", - Building::Observatory => "V", - Building::Fortress => "F", - Building::Port => "P", - Building::Temple => "T", +pub fn building_position(city: &City, center: Point, i: i32, building: Building) -> Point { + if matches!(building, Building::Port) { + let r: f32 = city + .position + .coordinate() + .directions_to(city.port_position.unwrap().coordinate())[0] + .to_radians_pointy(); + hex_ui::rotate_around_rad(center, 60.0, r * -1.0 + std::f32::consts::PI / 3.0) + } else { + hex_ui::rotate_around(center, 25.0, 90 * i) } } diff --git a/client/src/client.rs b/client/src/client.rs index 6936994d..cdd24f79 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -11,7 +11,6 @@ use crate::advance_ui::{pay_advance_dialog, show_advance_menu, show_free_advance use crate::client_state::{ActiveDialog, ShownPlayer, State, StateUpdate, StateUpdates}; use crate::collect_ui::{click_collect_option, collect_resources_dialog}; use crate::construct_ui::pay_construction_dialog; -use crate::dialog_ui::active_dialog_window; use crate::happiness_ui::{increase_happiness_dialog, increase_happiness_menu}; use crate::hex_ui::pixel_to_coordinate; use crate::log_ui::show_log; @@ -78,12 +77,12 @@ fn render(game: &Game, state: &mut State, features: &Features) -> StateUpdate { } updates.add(match &state.active_dialog { - ActiveDialog::None | ActiveDialog::MoveUnits(_) => StateUpdate::None, + ActiveDialog::None + | ActiveDialog::MoveUnits(_) + | ActiveDialog::WaitingForUpdate + | ActiveDialog::CulturalInfluence => StateUpdate::None, ActiveDialog::Log => show_log(game, player), - ActiveDialog::TileMenu(p) => show_tile_menu(game, *p, player), - ActiveDialog::WaitingForUpdate => { - active_dialog_window(player, "Waiting for update", |_ui| StateUpdate::None) - } + ActiveDialog::TileMenu(p) => show_tile_menu(game, *p, player, state), // playing actions ActiveDialog::IncreaseHappiness(h) => increase_happiness_menu(h, player), @@ -91,7 +90,9 @@ fn render(game: &Game, state: &mut State, features: &Features) -> StateUpdate { ActiveDialog::AdvancePayment(p) => pay_advance_dialog(p, player, game), ActiveDialog::ConstructionPayment(p) => pay_construction_dialog(game, p, player), ActiveDialog::CollectResources(c) => collect_resources_dialog(game, c, player), - ActiveDialog::RecruitUnitSelection(s) => recruit_unit_ui::select_dialog(game, s, player), + ActiveDialog::RecruitUnitSelection(s) => { + recruit_unit_ui::select_dialog(game, s, player, state) + } ActiveDialog::ReplaceUnits(r) => recruit_unit_ui::replace_dialog(game, r, player), ActiveDialog::CulturalInfluenceResolution(c) => { influence_ui::cultural_influence_resolution_dialog(c, player) @@ -123,10 +124,7 @@ fn render(game: &Game, state: &mut State, features: &Features) -> StateUpdate { updates.result() } -pub fn try_click(game: &Game, state: &State, player: &ShownPlayer) -> StateUpdate { - if !is_mouse_button_pressed(MouseButton::Left) { - return StateUpdate::None; - } +pub fn try_click(game: &Game, state: &mut State, player: &ShownPlayer) -> StateUpdate { let (x, y) = mouse_position(); let mouse_pos = state.camera.screen_to_world(vec2(x, y)); let pos = Position::from_coordinate(pixel_to_coordinate(mouse_pos)); @@ -134,6 +132,16 @@ pub fn try_click(game: &Game, state: &State, player: &ShownPlayer) -> StateUpdat return StateUpdate::None; } + if player.can_control { + if let ActiveDialog::CulturalInfluence = state.active_dialog { + return influence_ui::hover(pos, game, player, mouse_pos, state); + } + } + + if !is_mouse_button_pressed(MouseButton::Left) { + return StateUpdate::None; + } + if player.can_control { match &state.active_dialog { ActiveDialog::MoveUnits(s) => move_ui::click(pos, s, mouse_pos, game), diff --git a/client/src/client_state.rs b/client/src/client_state.rs index 857ce69f..879a055b 100644 --- a/client/src/client_state.rs +++ b/client/src/client_state.rs @@ -35,6 +35,7 @@ pub enum ActiveDialog { RecruitUnitSelection(RecruitAmount), ReplaceUnits(RecruitSelection), MoveUnits(MoveSelection), + CulturalInfluence, CulturalInfluenceResolution(CulturalInfluenceResolution), // status phase @@ -68,6 +69,7 @@ impl ActiveDialog { ActiveDialog::RecruitUnitSelection(_) => "recruit unit selection", ActiveDialog::ReplaceUnits(_) => "replace units", ActiveDialog::MoveUnits(_) => "move units", + ActiveDialog::CulturalInfluence => "cultural influence", ActiveDialog::CulturalInfluenceResolution(_) => "cultural influence resolution", ActiveDialog::FreeAdvance => "free advance", ActiveDialog::RazeSize1City => "raze size 1 city", @@ -83,41 +85,60 @@ impl ActiveDialog { } #[must_use] - pub fn help_message(&self) -> Option<&str> { + pub fn help_message(&self) -> Option { match self { - ActiveDialog::None => None, - ActiveDialog::TileMenu(_) => Some("Click on a tile to see options"), - ActiveDialog::Log => Some("Click on a log entry to see details"), - ActiveDialog::IncreaseHappiness(_) => Some("Click on a city to increase happiness"), - ActiveDialog::AdvanceMenu => Some("Click on an advance to see options"), - ActiveDialog::AdvancePayment(_) => Some("Click on an advance to pay"), - ActiveDialog::ConstructionPayment(_) => Some("Click on a city to pay for construction"), - ActiveDialog::CollectResources(_) => Some("Click on a city to collect resources"), - ActiveDialog::RecruitUnitSelection(_) => Some("Click on a unit to recruit"), - ActiveDialog::ReplaceUnits(_) => Some("Click on a unit to replace"), + ActiveDialog::None + | ActiveDialog::TileMenu(_) + | ActiveDialog::Log + | ActiveDialog::AdvanceMenu => None, + ActiveDialog::IncreaseHappiness(_) => { + Some("Click on a city to increase happiness".to_string()) + } + ActiveDialog::AdvancePayment(a) => { + Some(format!("Click on resources to pay for {}", a.name)) + } + ActiveDialog::ConstructionPayment(c) => { + Some(format!("Click on resources to pay for {}", c.name)) + } + ActiveDialog::CollectResources(_) => { + Some("Click on a tile to collect resources".to_string()) + } + ActiveDialog::RecruitUnitSelection(_) => Some("Click on a unit to recruit".to_string()), + ActiveDialog::ReplaceUnits(_) => Some("Click on a unit to replace".to_string()), ActiveDialog::MoveUnits(m) => { if m.start.is_some() { - Some("Click on a highlighted tile to move units") + Some("Click on a highlighted tile to move units".to_string()) } else { - Some("Click on a unit to move") + Some("Click on a unit to move".to_string()) } } - ActiveDialog::CulturalInfluenceResolution(_) => { - Some("Click on a city to resolve cultural influence") + ActiveDialog::CulturalInfluence => { + Some("Click on a building to influence its culture".to_string()) + } + ActiveDialog::CulturalInfluenceResolution(_) => Some("todo".to_string()), + ActiveDialog::FreeAdvance => { + Some("Click on an advance to take it for free".to_string()) + } + ActiveDialog::RazeSize1City => { + Some("Click on a city to raze it - or click cancel".to_string()) + } + ActiveDialog::CompleteObjectives => { + Some("Click on an objective to complete it".to_string()) } - ActiveDialog::FreeAdvance => Some("Click on an advance to take it for free"), - ActiveDialog::RazeSize1City => Some("Click on a city to raze it"), - ActiveDialog::CompleteObjectives => Some("Click on an objective to complete it"), ActiveDialog::DetermineFirstPlayer => { - Some("Click on a player to determine first player") + Some("Click on a player to determine first player".to_string()) } - ActiveDialog::ChangeGovernmentType => Some("Click on a government type to change"), - ActiveDialog::ChooseAdditionalAdvances(_) => Some("Click on an advance to choose it"), - ActiveDialog::PlayActionCard => Some("Click on an action card to play it"), - ActiveDialog::PlaceSettler => Some("Click on a tile to place a settler"), - ActiveDialog::Retreat => Some("Click on a unit to retreat"), - ActiveDialog::RemoveCasualties(_) => Some("Click on a unit to remove it"), - ActiveDialog::WaitingForUpdate => panic!("no help message for dialog"), + ActiveDialog::ChangeGovernmentType => { + Some("Click on a government type to change - or click cancel".to_string()) + } + ActiveDialog::ChooseAdditionalAdvances(_) => { + Some("Click on an advance to choose it".to_string()) + } + ActiveDialog::PlayActionCard => Some("Click on an action card to play it".to_string()), + ActiveDialog::PlaceSettler => Some("Click on a tile to place a settler".to_string()), + ActiveDialog::Retreat => Some("Click on a unit to retreat".to_string()), + ActiveDialog::RemoveCasualties(_) => Some("Click on a unit to remove it".to_string()), + ActiveDialog::WaitingForUpdate => Some("Waiting for server update".to_string()), } } @@ -284,6 +305,11 @@ pub struct MousePosition { pub time: f64, } +pub enum CameraMode { + Screen, + World, +} + pub struct State { pub assets: Assets, pub control_player: Option, @@ -291,6 +317,7 @@ pub struct State { pub active_dialog: ActiveDialog, pub pending_update: Option, pub camera: Camera2D, + pub camera_mode: CameraMode, pub zoom: f32, pub offset: Vec2, pub screen_size: Vec2, @@ -311,6 +338,7 @@ impl State { camera: Camera2D { ..Default::default() }, + camera_mode: CameraMode::Screen, zoom: ZOOM, offset: OFFSET, screen_size: vec2(0., 0.), @@ -476,6 +504,10 @@ impl State { } pub fn draw_text(&self, text: &str, x: f32, y: f32) { + self.draw_text_with_color(text, x, y, BLACK); + } + + pub fn draw_text_with_color(&self, text: &str, x: f32, y: f32, color: Color) { draw_text_ex( text, x, @@ -483,14 +515,42 @@ impl State { TextParams { font: Some(&self.assets.font), font_size: FONT_SIZE, - color: BLACK, + color, ..Default::default() }, ); } - // fn execute_status_phase(&mut self, game: &Game, action: StatusPhaseAction) -> ActiveDialog { - // self.update(game, StateUpdate::status_phase(action)); - // ActiveDialog::None - // } + pub fn set_screen_camera(&mut self) { + set_default_camera(); + self.camera_mode = CameraMode::Screen; + } + + pub fn set_world_camera(&mut self) { + set_camera(&self.camera); + self.camera_mode = CameraMode::World; + } + + pub fn set_camera(&self) { + match self.camera_mode { + CameraMode::Screen => set_default_camera(), + CameraMode::World => set_camera(&self.camera), + }; + } + + #[must_use] + pub fn world_to_screen(&self, point: Vec2) -> Vec2 { + match self.camera_mode { + CameraMode::Screen => point, + CameraMode::World => self.camera.world_to_screen(point), + } + } + + #[must_use] + pub fn screen_to_world(&self, point: Vec2) -> Vec2 { + match self.camera_mode { + CameraMode::Screen => point, + CameraMode::World => self.camera.screen_to_world(point), + } + } } diff --git a/client/src/construct_ui.rs b/client/src/construct_ui.rs index cef753f0..e543af42 100644 --- a/client/src/construct_ui.rs +++ b/client/src/construct_ui.rs @@ -1,9 +1,7 @@ use std::cmp; use macroquad::math::{i32, u32}; -use macroquad::ui::Ui; -use crate::city_ui::CityMenu; use server::action::Action; use server::city::City; use server::city_pieces::Building; @@ -21,42 +19,8 @@ use crate::recruit_unit_ui::RecruitSelection; use crate::resource_ui::{new_resource_map, ResourceType}; use crate::select_ui::CountSelector; -pub fn add_construct_button( - game: &Game, - menu: &CityMenu, - ui: &mut Ui, - building: &Building, - name: &str, -) -> StateUpdate { - let owner = menu.get_city_owner(game); - let city = menu.get_city(game); - if menu.is_city_owner() && menu.player.can_play_action && city.can_construct(building, owner) { - for pos in building_positions(building, city, &game.map) { - if ui.button( - None, - format!( - "Build {}{}", - name, - pos.map_or(String::new(), |p| format!(" at {p}")) - ), - ) { - return StateUpdate::SetDialog(ActiveDialog::ConstructionPayment( - ConstructionPayment::new( - game, - name, - menu.player.index, - menu.city_position, - ConstructionProject::Building(building.clone(), pos), - ), - )); - } - } - } - StateUpdate::None -} - -fn building_positions(building: &Building, city: &City, map: &Map) -> Vec> { - if building != &Building::Port { +pub fn building_positions(building: Building, city: &City, map: &Map) -> Vec> { + if building != Building::Port { return vec![None]; } @@ -72,27 +36,6 @@ fn building_positions(building: &Building, city: &City, map: &Map) -> Vec