From e663facbeb9ee5b47d8f360d1531e25925d9af55 Mon Sep 17 00:00:00 2001
From: Louis Capitanchik <contact@louiscap.co>
Date: Thu, 8 Dec 2022 02:48:51 +0000
Subject: [PATCH] Update kayak_ui, include distance travelled in save data

---
 Cargo.lock                                  |   6 +-
 Cargo.toml                                  |   4 +-
 assets/resources.apack                      |   4 +-
 game_core/src/persistance/save_file.rs      |  69 ++++++++-----
 game_core/src/ui/components/a_text_box.rs   |   2 +-
 game_core/src/ui/components/button.rs       |   2 +-
 game_core/src/ui/components/debug_info.rs   |  35 +------
 game_core/src/ui/components/h_divider.rs    |  49 +++++++++
 game_core/src/ui/components/image_button.rs |   2 +-
 game_core/src/ui/components/inset_icon.rs   |   4 +-
 game_core/src/ui/components/mod.rs          |   2 +
 game_core/src/ui/components/panel.rs        |  57 +----------
 game_core/src/ui/screens/in_game.rs         |   7 +-
 game_core/src/ui/screens/main_menu.rs       |   4 +-
 game_core/src/ui/sync/mod.rs                |  23 ++---
 game_core/src/ui/sync/sync_stats.rs         |  30 ++++++
 game_core/src/ui/sync/sync_travel.rs        |   2 +-
 game_core/src/ui/utilities.rs               |  15 ++-
 game_core/src/ui/widgets/encounter_panel.rs |   4 +-
 game_core/src/ui/widgets/mod.rs             |   2 +
 game_core/src/ui/widgets/shop_panel.rs      |   4 +-
 game_core/src/ui/widgets/stats_panel.rs     | 105 ++++++++++++++++++++
 game_core/src/ui/widgets/town_menu.rs       |   4 +-
 game_core/src/ui/widgets/transit_panel.rs   |   4 +-
 game_core/src/world/mod.rs                  |  22 ++--
 game_core/src/world/spawning.rs             |  43 ++++----
 game_core/src/world/trading.rs              |   6 +-
 game_core/src/world/travel.rs               |  10 +-
 28 files changed, 339 insertions(+), 182 deletions(-)
 create mode 100644 game_core/src/ui/components/h_divider.rs
 create mode 100644 game_core/src/ui/sync/sync_stats.rs
 create mode 100644 game_core/src/ui/widgets/stats_panel.rs

diff --git a/Cargo.lock b/Cargo.lock
index 69f109e..72e7089 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2080,7 +2080,7 @@ dependencies = [
 [[package]]
 name = "kayak_font"
 version = "0.1.0"
-source = "git+https://github.com/StarArawn/kayak_ui.git?rev=220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7#220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7"
+source = "git+https://github.com/StarArawn/kayak_ui.git?rev=a85cab76839f22e5f5a1002b864bad3923e0f375#a85cab76839f22e5f5a1002b864bad3923e0f375"
 dependencies = [
  "anyhow",
  "bevy",
@@ -2097,7 +2097,7 @@ dependencies = [
 [[package]]
 name = "kayak_ui"
 version = "0.1.0"
-source = "git+https://github.com/StarArawn/kayak_ui.git?rev=220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7#220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7"
+source = "git+https://github.com/StarArawn/kayak_ui.git?rev=a85cab76839f22e5f5a1002b864bad3923e0f375#a85cab76839f22e5f5a1002b864bad3923e0f375"
 dependencies = [
  "bevy",
  "bitflags",
@@ -2115,7 +2115,7 @@ dependencies = [
 [[package]]
 name = "kayak_ui_macros"
 version = "0.1.0"
-source = "git+https://github.com/StarArawn/kayak_ui.git?rev=220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7#220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7"
+source = "git+https://github.com/StarArawn/kayak_ui.git?rev=a85cab76839f22e5f5a1002b864bad3923e0f375#a85cab76839f22e5f5a1002b864bad3923e0f375"
 dependencies = [
  "find-crate",
  "proc-macro-crate",
diff --git a/Cargo.toml b/Cargo.toml
index 2828020..5c5bbe7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,8 +19,8 @@ iyes_loopless = "0.9.1"
 micro_musicbox = { version = "0.5.0", features = ["mp3"] }
 micro_banimate = { version = "0.2.1", features = ["ecs_tilemap"] }
 
-kayak_ui = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui" }
-kayak_font = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui.git" }
+kayak_ui = { rev = "a85cab76839f22e5f5a1002b864bad3923e0f375", git = "https://github.com/StarArawn/kayak_ui" }
+kayak_font = { rev = "a85cab76839f22e5f5a1002b864bad3923e0f375", git = "https://github.com/StarArawn/kayak_ui.git" }
 
 [workspace.dependencies.bevy]
 version = "0.9.0"
diff --git a/assets/resources.apack b/assets/resources.apack
index c99c8b4..bfc8a3e 100644
--- a/assets/resources.apack
+++ b/assets/resources.apack
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:edc6d705e89fdbbb006ec4b772f1299fdb5e7a6be207261d4dee8d070e6b402c
-size 1578160
+oid sha256:a1edb177ccd8eef0f5b3cdcb4ea5e2804c88a62bf4e5cee12f98a7bfbc1b5261
+size 1579824
diff --git a/game_core/src/persistance/save_file.rs b/game_core/src/persistance/save_file.rs
index c0d7466..a409640 100644
--- a/game_core/src/persistance/save_file.rs
+++ b/game_core/src/persistance/save_file.rs
@@ -14,8 +14,8 @@ use crate::persistance::fs_utils::{get_root_save_dir, AUTOSAVE_NAME};
 use crate::states::Player;
 use crate::system::flow::AppState;
 use crate::world::{
-	CurrentResidence, EncounterState, HungerState, PendingLoadState, TradingState, TravelPath,
-	TravelTarget,
+	CurrentResidence, DistanceTravelled, EncounterState, HungerState, PendingLoadState,
+	TradingState, TravelPath, TravelTarget,
 };
 
 #[derive(Serialize, Deserialize, Debug, Resource)]
@@ -23,8 +23,12 @@ pub struct PersistenceState {
 	pub player_location: Vec3,
 	pub player_inventory: TradingState,
 	pub player_hunger: HungerState,
+	#[serde(default)]
 	pub travel_path: Option<TravelPath>,
+	#[serde(default)]
 	pub travel_target: Option<TravelTarget>,
+	#[serde(default)]
+	pub player_total_distance: DistanceTravelled,
 	pub previous_location: CurrentResidence,
 	pub encounter_state: EncounterState,
 	pub town_states: HashMap<String, (TradingState, HungerState)>,
@@ -52,31 +56,44 @@ impl LoadFileEvent {
 	}
 }
 
+type PlayerInfoQuery<'a, 'b> = Query<
+	'a,
+	'b,
+	(
+		&'static Transform,
+		Option<&'static TravelPath>,
+		Option<&'static TravelTarget>,
+		&'static CurrentResidence,
+		&'static TradingState,
+		&'static HungerState,
+		&'static DistanceTravelled,
+	),
+	With<Player>,
+>;
+
 pub fn sync_state_to_persistence(
 	mut commands: Commands,
 	mut state: Option<ResMut<PersistenceState>>,
-	player_query: Query<
-		(
-			&Transform,
-			Option<&TravelPath>,
-			Option<&TravelTarget>,
-			&CurrentResidence,
-		),
-		With<Player>,
-	>,
+	player_query: PlayerInfoQuery,
 	encounters: Res<EncounterState>,
-	hunger: Option<Res<HungerState>>,
-	trading: Option<Res<TradingState>>,
 ) {
 	match state {
 		Some(mut state) => {
-			if let Some((transform, maybe_travel, maybe_target, residence)) =
-				player_query.iter().next()
+			if let Some((
+				transform,
+				maybe_travel,
+				maybe_target,
+				residence,
+				trading,
+				hunger,
+				distance,
+			)) = player_query.iter().next()
 			{
 				*state = PersistenceState {
 					player_location: transform.translation,
-					player_inventory: trading.map(|r| r.clone()).unwrap_or_default(),
-					player_hunger: hunger.map(|r| r.clone()).unwrap_or_default(),
+					player_inventory: trading.clone(),
+					player_hunger: hunger.clone(),
+					player_total_distance: *distance,
 					travel_path: maybe_travel.cloned(),
 					travel_target: maybe_target.cloned(),
 					previous_location: residence.clone(),
@@ -86,13 +103,21 @@ pub fn sync_state_to_persistence(
 			}
 		}
 		None => {
-			if let Some((transform, maybe_travel, maybe_target, residence)) =
-				player_query.iter().next()
+			if let Some((
+				transform,
+				maybe_travel,
+				maybe_target,
+				residence,
+				trading,
+				hunger,
+				distance,
+			)) = player_query.iter().next()
 			{
 				commands.insert_resource(PersistenceState {
 					player_location: transform.translation,
-					player_inventory: trading.map(|r| r.clone()).unwrap_or_default(),
-					player_hunger: hunger.map(|r| r.clone()).unwrap_or_default(),
+					player_inventory: trading.clone(),
+					player_hunger: hunger.clone(),
+					player_total_distance: *distance,
 					travel_path: maybe_travel.cloned(),
 					travel_target: maybe_target.cloned(),
 					previous_location: residence.clone(),
@@ -136,9 +161,7 @@ pub fn handle_save_event(
 pub fn handle_load_event(mut commands: Commands, mut events: ResMut<Events<LoadFileEvent>>) {
 	let root_dir = get_root_save_dir().expect("Could not find root dir for saves");
 	for event in events.drain() {
-		log::info!("Trying to load file");
 		let path = root_dir.join(event.filename.as_deref().unwrap_or(AUTOSAVE_NAME));
-		log::info!("Path is {}", path.display());
 		match std::fs::File::open(path) {
 			Ok(file) => match serde_json::from_reader::<File, PersistenceState>(file) {
 				Ok(value) => {
diff --git a/game_core/src/ui/components/a_text_box.rs b/game_core/src/ui/components/a_text_box.rs
index 8cab85a..a418532 100644
--- a/game_core/src/ui/components/a_text_box.rs
+++ b/game_core/src/ui/components/a_text_box.rs
@@ -411,7 +411,7 @@ pub fn render_text_box_widget(
 						</ElementBundle>
 					</ClipBundle>
 				</BackgroundBundle>
-			}
+			};
 		}
 	}
 
diff --git a/game_core/src/ui/components/button.rs b/game_core/src/ui/components/button.rs
index ba2adbb..09d0882 100644
--- a/game_core/src/ui/components/button.rs
+++ b/game_core/src/ui/components/button.rs
@@ -287,7 +287,7 @@ pub fn render_button_widget(
 						);
 					}}
 				</NinePatchBundle>
-			}
+			};
 		}
 	}
 	true
diff --git a/game_core/src/ui/components/debug_info.rs b/game_core/src/ui/components/debug_info.rs
index 8e5e901..e546da0 100644
--- a/game_core/src/ui/components/debug_info.rs
+++ b/game_core/src/ui/components/debug_info.rs
@@ -31,40 +31,7 @@ pub fn render_debug_info(
 		<ElementBundle styles={element_style}>
 			<TextWidgetBundle text={simple_text(VERSION, "mono", 32.0)} />
 		</ElementBundle>
-	}
+	};
 
 	true
 }
-
-// #[widget]
-// pub fn DebugInfo() {
-// 	// let f = WidgetN
-// 	let container_style = Style {
-// 		position_type: PositionType::SelfDirected.into(),
-// 		bottom: px(20.0),
-// 		right: px(20.0),
-// 		left: stretch(1.0),
-// 		top: stretch(1.0),
-// 		width: px(120.0),
-// 		height: Units::Auto.into(),
-// 		..Default::default()
-// 	};
-// 	let text_style = Style {
-// 		color: Color::WHITE.into(),
-// 		font_size: (24.0).into(),
-// 		left: stretch(1.0),
-// 		font: context
-// 			.get_font_id("mono")
-// 			.map(StyleProp::Value)
-// 			.unwrap_or(StyleProp::Unset),
-// 		..Default::default()
-// 	};
-//
-// 	let val = env!("CARGO_PKG_VERSION");
-//
-// 	rsx! {
-// 		<Element styles={Some(container_style)}>
-// 			<Text styles={Some(text_style)} content={format!("v{}", val)} />
-// 		</Element>
-// 	}
-// }
diff --git a/game_core/src/ui/components/h_divider.rs b/game_core/src/ui/components/h_divider.rs
new file mode 100644
index 0000000..f2b75d9
--- /dev/null
+++ b/game_core/src/ui/components/h_divider.rs
@@ -0,0 +1,49 @@
+use bevy::prelude::*;
+use kayak_ui::prelude::{
+	ComputedStyles, KStyle, KayakWidgetContext, RenderCommand, StyleProp, Widget,
+};
+
+use crate::basic_widget;
+use crate::ui::prelude::{px, stretch, value};
+
+#[derive(Component, Debug, Clone, PartialEq)]
+pub struct HDividerWidgetProps {
+	pub width: f32,
+	pub padding: f32,
+	pub color: Color,
+}
+
+impl Default for HDividerWidgetProps {
+	fn default() -> Self {
+		Self {
+			width: 1.0,
+			padding: 5.0,
+			color: Color::ANTIQUE_WHITE,
+		}
+	}
+}
+
+impl Widget for HDividerWidgetProps {}
+
+basic_widget!(HDividerWidgetProps => HDividerWidget);
+
+pub fn render_h_divider(
+	In((_, entity)): In<(KayakWidgetContext, Entity)>,
+	_: Commands,
+	mut query: Query<(&HDividerWidgetProps, &KStyle, &mut ComputedStyles)>,
+) -> bool {
+	if let Ok((props, style, mut computed)) = query.get_mut(entity) {
+		*computed = KStyle {
+			render_command: value(RenderCommand::Quad),
+			background_color: StyleProp::Value(props.color),
+			width: px(props.width),
+			left: px(props.padding),
+			right: px(props.padding),
+			height: stretch(1.0),
+			..Default::default()
+		}
+		.with_style(style)
+		.into();
+	}
+	true
+}
diff --git a/game_core/src/ui/components/image_button.rs b/game_core/src/ui/components/image_button.rs
index da5eb6d..fc6626a 100644
--- a/game_core/src/ui/components/image_button.rs
+++ b/game_core/src/ui/components/image_button.rs
@@ -245,7 +245,7 @@ pub fn render_image_button_widget(
 						ImageButtonContent::None => {}
 					} }
 				</NinePatchBundle>
-			}
+			};
 		}
 	}
 	true
diff --git a/game_core/src/ui/components/inset_icon.rs b/game_core/src/ui/components/inset_icon.rs
index 89814c2..e05e25d 100644
--- a/game_core/src/ui/components/inset_icon.rs
+++ b/game_core/src/ui/components/inset_icon.rs
@@ -80,7 +80,7 @@ pub fn render_inset_icon_widget(
 						image={KImage(handle)}
 						styles={image_styles}
 					/>
-				}
+				};
 			}
 			IconContent::Atlas(name, index) => {
 				let atlas_handle = assets.atlas(name);
@@ -99,7 +99,7 @@ pub fn render_inset_icon_widget(
 							}}
 							styles={image_styles}
 						/>
-					}
+					};
 				}
 			}
 			IconContent::None => {}
diff --git a/game_core/src/ui/components/mod.rs b/game_core/src/ui/components/mod.rs
index 7a1147c..31e028e 100644
--- a/game_core/src/ui/components/mod.rs
+++ b/game_core/src/ui/components/mod.rs
@@ -3,6 +3,7 @@
 mod a_text_box;
 mod button;
 mod debug_info;
+mod h_divider;
 mod image_button;
 mod inset_icon;
 mod panel;
@@ -15,6 +16,7 @@ pub use self::button::{
 	button_props, render_button_widget, ButtonWidget, ButtonWidgetProps, ButtonWidgetState,
 };
 pub use self::debug_info::{render_debug_info, DebugInfoProps, DebugInfoWidget};
+pub use self::h_divider::{render_h_divider, HDividerWidget, HDividerWidgetProps};
 pub use self::image_button::{
 	render_image_button_widget, ImageButtonWidget, ImageButtonWidgetProps, ImageButtonWidgetState,
 };
diff --git a/game_core/src/ui/components/panel.rs b/game_core/src/ui/components/panel.rs
index 45eca81..ac1f19e 100644
--- a/game_core/src/ui/components/panel.rs
+++ b/game_core/src/ui/components/panel.rs
@@ -36,7 +36,6 @@ pub fn render_panel_widget(
 		};
 
 		*computed = KStyle {
-			render_command: value(RenderCommand::Layout),
 			min_height: px(edge_size * 2.0 + 8.0),
 			min_width: px(edge_size * 2.0 + 8.0),
 			padding: value(Edge::all(Units::Stretch(0.0))),
@@ -65,61 +64,7 @@ pub fn render_panel_widget(
 				styles={inner_style}
 				children={children.clone()}
 			/>
-		}
+		};
 	}
 	true
 }
-
-// #[widget]
-// pub fn Panel(props: PanelProps) {
-// 	let styles = Some(
-// 		Style::default()
-// 			.with_style(Style {
-// 				min_height: Units::Pixels(72.0).into(),
-// 				min_width: Units::Pixels(72.0).into(),
-// 				..Default::default()
-// 			})
-// 			.with_style(props.get_styles())
-// 			.with_style(Style {
-// 				color: Color::WHITE.into(),
-// 				padding: StyleProp::Value(Edge::all(Units::Pixels(32.0))),
-// 				..Default::default()
-// 			}),
-// 	);
-//
-// 	let children = props.get_children();
-// 	let patch = context.get_ninepatch("panel");
-//
-// 	rsx! {
-// 		<NinePatch border={Edge::all(4.0 * 8.0)} handle={patch} styles={styles}>
-// 			{ children }
-// 		</NinePatch>
-// 	}
-// }
-//
-// #[widget]
-// pub fn BasicPanel(props: PanelProps) {
-// 	let styles = Some(
-// 		Style::default()
-// 			.with_style(Style {
-// 				min_height: Units::Pixels(28.0).into(),
-// 				min_width: Units::Pixels(28.0).into(),
-// 				..Default::default()
-// 			})
-// 			.with_style(props.get_styles())
-// 			.with_style(Style {
-// 				color: Color::WHITE.into(),
-// 				padding: StyleProp::Value(Edge::all(Units::Pixels(12.0))),
-// 				..Default::default()
-// 			}),
-// 	);
-//
-// 	let children = props.get_children();
-// 	let patch = context.get_ninepatch("basic_panel");
-//
-// 	rsx! {
-// 		<NinePatch border={Edge::all(12.0)} handle={patch} styles={styles} on_event={props.get_on_event()} on_layout={props.get_on_layout()}>
-// 			{ children }
-// 		</NinePatch>
-// 	}
-// }
diff --git a/game_core/src/ui/screens/in_game.rs b/game_core/src/ui/screens/in_game.rs
index 1ee476d..e152050 100644
--- a/game_core/src/ui/screens/in_game.rs
+++ b/game_core/src/ui/screens/in_game.rs
@@ -75,8 +75,11 @@ pub fn render_game_panels(
 				}
 			}}
 
+			{ if show_distance_panel {
+				constructor! { <StatsPanel /> }
+			}}
 		</ElementBundle>
-	}
+	};
 
 	true
 }
@@ -89,7 +92,7 @@ pub fn render_in_game_ui(mut commands: Commands) {
 		<KayakAppBundle>
 			<InGameLayout />
 		</KayakAppBundle>
-	}
+	};
 
 	commands.spawn((UICameraBundle::new(widget_context), StateUIRoot));
 }
diff --git a/game_core/src/ui/screens/main_menu.rs b/game_core/src/ui/screens/main_menu.rs
index ed2f0c3..d5b0a69 100644
--- a/game_core/src/ui/screens/main_menu.rs
+++ b/game_core/src/ui/screens/main_menu.rs
@@ -119,7 +119,7 @@ pub fn render_main_menu_layout(
 				/>
 			</ElementBundle>
 		</ElementBundle>
-	}
+	};
 
 	true
 }
@@ -132,7 +132,7 @@ pub fn render_menu_ui(mut commands: Commands) {
 		<KayakAppBundle>
 			<MainMenuLayout />
 		</KayakAppBundle>
-	}
+	};
 
 	commands.spawn((UICameraBundle::new(widget_context), StateUIRoot));
 }
diff --git a/game_core/src/ui/sync/mod.rs b/game_core/src/ui/sync/mod.rs
index d6358a7..0dc64da 100644
--- a/game_core/src/ui/sync/mod.rs
+++ b/game_core/src/ui/sync/mod.rs
@@ -3,24 +3,23 @@ use iyes_loopless::prelude::ConditionSet;
 
 use crate::system::flow::AppState;
 
+mod sync_stats;
 mod sync_travel;
 
 pub struct UISyncPlugin;
 impl Plugin for UISyncPlugin {
 	fn build(&self, app: &mut App) {
-		app.insert_resource(sync_travel::UITravelInfo {
-			travel_options: Vec::new(),
-			is_in_town: false,
-			current_town: None,
-			distance_remaining: 0.0,
-		})
-		.add_system_set(
-			ConditionSet::new()
-				.run_in_state(AppState::InGame)
-				.with_system(sync_travel::sync_player_travel_info_to_ui)
-				.into(),
-		);
+		app.init_resource::<UITravelInfo>()
+			.init_resource::<UIStatsData>()
+			.add_system_set(
+				ConditionSet::new()
+					.run_in_state(AppState::InGame)
+					.with_system(sync_travel::sync_player_travel_info_to_ui)
+					.with_system(sync_stats::sync_player_stats_info_to_ui)
+					.into(),
+			);
 	}
 }
 
+pub use sync_stats::UIStatsData;
 pub use sync_travel::UITravelInfo;
diff --git a/game_core/src/ui/sync/sync_stats.rs b/game_core/src/ui/sync/sync_stats.rs
new file mode 100644
index 0000000..c36f2a7
--- /dev/null
+++ b/game_core/src/ui/sync/sync_stats.rs
@@ -0,0 +1,30 @@
+use bevy::prelude::*;
+
+use crate::states::Player;
+use crate::world::{DistanceTravelled, HungerState, TradingState};
+
+#[derive(Resource, Debug, Default, Copy, Clone)]
+pub struct UIStatsData {
+	pub money: usize,
+	pub distance_travelled: f32,
+	pub sustenance: usize,
+}
+
+pub fn sync_player_stats_info_to_ui(
+	player_query: Query<(&DistanceTravelled, &TradingState, &HungerState), With<Player>>,
+	mut ui_info: ResMut<UIStatsData>,
+) {
+	if let Ok((distance, trading, hunger)) = player_query.get_single() {
+		if ui_info.money != trading.gold.max(0) as usize {
+			ui_info.money = trading.gold.max(0) as usize;
+		}
+
+		if ui_info.distance_travelled != distance.0 {
+			ui_info.distance_travelled = distance.0;
+		}
+
+		if ui_info.sustenance != hunger.sustenance {
+			ui_info.sustenance = hunger.sustenance;
+		}
+	}
+}
diff --git a/game_core/src/ui/sync/sync_travel.rs b/game_core/src/ui/sync/sync_travel.rs
index 585f492..522ce4f 100644
--- a/game_core/src/ui/sync/sync_travel.rs
+++ b/game_core/src/ui/sync/sync_travel.rs
@@ -4,7 +4,7 @@ use bevy::prelude::*;
 use crate::states::Player;
 use crate::world::{CurrentResidence, MapQuery, TownPaths, TravelPath};
 
-#[derive(Resource, Clone)]
+#[derive(Resource, Clone, Default)]
 pub struct UITravelInfo {
 	pub distance_remaining: f32,
 	pub is_in_town: bool,
diff --git a/game_core/src/ui/utilities.rs b/game_core/src/ui/utilities.rs
index 8c5613e..8a4e3fb 100644
--- a/game_core/src/ui/utilities.rs
+++ b/game_core/src/ui/utilities.rs
@@ -157,7 +157,7 @@ pub mod context {
 
 	use crate::register_widget;
 	use crate::ui::components::*;
-	use crate::ui::sync::UITravelInfo;
+	use crate::ui::sync::{UIStatsData, UITravelInfo};
 	use crate::ui::widgets::*;
 	use crate::world::EncounterState;
 
@@ -196,6 +196,12 @@ pub mod context {
 				EmptyState,
 				render_v_divider
 			);
+			register_widget!(
+				widget_context,
+				HDividerWidgetProps,
+				EmptyState,
+				render_h_divider
+			);
 			register_widget!(
 				widget_context,
 				ATextBoxProps,
@@ -222,6 +228,13 @@ pub mod context {
 				UITravelInfo,
 				render_transit_panel
 			);
+			register_widget_with_resource!(
+				widget_context,
+				StatsPanelProps,
+				EmptyState,
+				UIStatsData,
+				render_stats_panel
+			);
 			register_widget_with_resource!(
 				widget_context,
 				EncounterPanelProps,
diff --git a/game_core/src/ui/widgets/encounter_panel.rs b/game_core/src/ui/widgets/encounter_panel.rs
index 12217f0..39fa308 100644
--- a/game_core/src/ui/widgets/encounter_panel.rs
+++ b/game_core/src/ui/widgets/encounter_panel.rs
@@ -94,6 +94,8 @@ pub fn render_encounter_panel(
 										constructor! {
 											<BackgroundBundle
 												styles={KStyle {
+
+													pointer_events: value(PointerEvents::None),
 													render_command: value(RenderCommand::Quad),
 													layout_type: value(LayoutType::Row),
 													height: value(Units::Auto),
@@ -151,7 +153,7 @@ pub fn render_encounter_panel(
 						}
 					</ElementBundle>
 				</ElementBundle>
-			}
+			};
 		}
 		EncounterState::Consequence(..) => {}
 		EncounterState::NoEncounter => {}
diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs
index d3e04d4..c706e56 100644
--- a/game_core/src/ui/widgets/mod.rs
+++ b/game_core/src/ui/widgets/mod.rs
@@ -1,9 +1,11 @@
 mod encounter_panel;
 mod shop_panel;
+mod stats_panel;
 mod town_menu;
 mod transit_panel;
 
 pub use encounter_panel::{render_encounter_panel, EncounterPanel, EncounterPanelProps};
+pub use stats_panel::{render_stats_panel, StatsPanel, StatsPanelProps};
 pub use town_menu::{
 	render_town_menu_panel, TownMenuPanel, TownMenuPanelProps, TownMenuPanelState,
 };
diff --git a/game_core/src/ui/widgets/shop_panel.rs b/game_core/src/ui/widgets/shop_panel.rs
index 00c8ed3..b008d44 100644
--- a/game_core/src/ui/widgets/shop_panel.rs
+++ b/game_core/src/ui/widgets/shop_panel.rs
@@ -102,13 +102,13 @@ pub fn render_transit_panel(
 									props={ButtonWidgetProps::text(format!("{}: {}", &place, format_ui_distance(*distance)), 28.0)}
 									on_event={buysell_button_factory(place.clone())}
 								/>
-							}
+							};
 						}
 					}
 				</ScrollBoxBundle>
 			</ScrollContextProviderBundle>
 		</ElementBundle>
-	}
+	};
 
 	true
 }
diff --git a/game_core/src/ui/widgets/stats_panel.rs b/game_core/src/ui/widgets/stats_panel.rs
new file mode 100644
index 0000000..c1c7432
--- /dev/null
+++ b/game_core/src/ui/widgets/stats_panel.rs
@@ -0,0 +1,105 @@
+use bevy::prelude::*;
+use kayak_ui::prelude::*;
+use kayak_ui::widgets::{BackgroundBundle, ElementBundle, TextProps, TextWidgetBundle};
+
+use crate::system::utilities::format_ui_distance;
+use crate::ui::sync::UIStatsData;
+use crate::ui::{components::*, prelude::*, widgets::*};
+use crate::{basic_widget, empty_props};
+
+empty_props!(StatsPanelProps);
+basic_widget!(StatsPanelProps => StatsPanel);
+
+pub fn render_stats_panel(
+	In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
+	mut commands: Commands,
+	ui_data: Res<UIStatsData>,
+) -> bool {
+	let parent_id = Some(entity);
+	// let root_style = KStyle {
+	// 	position_type: value(KPositionType::SelfDirected),
+	// 	top: stretch(1.0),
+	// 	left: stretch(1.0),
+	// 	right: stretch(1.0),
+	// 	bottom: px(50.0),
+	// 	height: px(50.0),
+	// 	width: stretch(0.6),
+	// 	max_width: px(200.0),
+	// 	layout_type: value(LayoutType::Row),
+	// 	padding: value(Edge::all(Units::Stretch(1.0))),
+	// 	..Default::default()
+	// };
+	//
+	let root_style = KStyle {
+		position_type: value(KPositionType::SelfDirected),
+		top: px(15.0),
+		left: stretch(1.0),
+		right: stretch(1.0),
+		bottom: stretch(1.0),
+		height: px(70.0),
+		layout_type: value(LayoutType::Column),
+		..Default::default()
+	};
+	//
+	let distance_travelled = format_ui_distance(ui_data.distance_travelled);
+
+	let inner_container_style = KStyle {
+		height: px(60.0),
+		padding: edge_px(5.0),
+		col_between: px(5.0),
+		left: stretch(1.0),
+		right: stretch(1.0),
+		layout_type: value(LayoutType::Row),
+		..Default::default()
+	};
+
+	rsx! {
+		<PanelWidget styles={root_style}>
+			<ElementBundle styles={inner_container_style}>
+				<InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 14)}} />
+				<TextWidgetBundle
+					text={TextProps {
+						content: distance_travelled,
+						size: 28.0,
+						..Default::default()
+					}}
+					styles={KStyle {
+						color: value(Color::BLACK),
+						bottom: px(6.0),
+						..Default::default()
+					}}
+				/>
+
+				<InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 8)}} />
+				<TextWidgetBundle
+					text={TextProps {
+						content: format!("{}g", ui_data.money),
+						size: 28.0,
+						..Default::default()
+					}}
+					styles={KStyle {
+						color: value(Color::BLACK),
+						bottom: px(6.0),
+						..Default::default()
+					}}
+				/>
+
+				<InsetIconWidget props={InsetIconProps { size: 28.0, image: IconContent::Atlas(String::from("icons"), 15)}} />
+				<TextWidgetBundle
+					text={TextProps {
+						content: format!("{}", ui_data.sustenance),
+						size: 28.0,
+						..Default::default()
+					}}
+					styles={KStyle {
+						color: value(Color::BLACK),
+						bottom: px(6.0),
+						..Default::default()
+					}}
+				/>
+			</ElementBundle>
+		</PanelWidget>
+	};
+
+	true
+}
diff --git a/game_core/src/ui/widgets/town_menu.rs b/game_core/src/ui/widgets/town_menu.rs
index f015185..c204ab8 100644
--- a/game_core/src/ui/widgets/town_menu.rs
+++ b/game_core/src/ui/widgets/town_menu.rs
@@ -173,14 +173,14 @@ pub fn render_town_menu_panel(
 						TownMenuTab::Travel => {
 							constructor! {
 								<TransitPanel />
-							}
+							};
 						},
 						TownMenuTab::Merchant => {}
 						TownMenuTab::Tavern => {}
 					}
 				}
 			</PanelWidget>
-		}
+		};
 	}
 
 	true
diff --git a/game_core/src/ui/widgets/transit_panel.rs b/game_core/src/ui/widgets/transit_panel.rs
index d610107..4015846 100644
--- a/game_core/src/ui/widgets/transit_panel.rs
+++ b/game_core/src/ui/widgets/transit_panel.rs
@@ -102,13 +102,13 @@ pub fn render_transit_panel(
 									props={ButtonWidgetProps::text(format!("{}: {}", &place, format_ui_distance(*distance)), 28.0)}
 									on_event={transit_button_factory(place.clone())}
 								/>
-							}
+							};
 						}
 					}
 				</ScrollBoxBundle>
 			</ScrollContextProviderBundle>
 		</ElementBundle>
-	}
+	};
 
 	true
 }
diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs
index 0f03c26..91dbefc 100644
--- a/game_core/src/world/mod.rs
+++ b/game_core/src/world/mod.rs
@@ -2,9 +2,6 @@ use bevy::app::App;
 use bevy::prelude::{Commands, Plugin};
 use iyes_loopless::prelude::{AppLooplessStateExt, ConditionSet};
 
-use crate::system::flow::AppState;
-use crate::world::utils::ActiveLevel;
-
 mod debug;
 mod encounters;
 mod generators;
@@ -15,6 +12,17 @@ mod travel;
 mod utils;
 mod world_query;
 
+pub use encounters::{EncounterState, WorldZones};
+pub use spawning::PendingLoadState;
+pub use towns::{CurrentResidence, PathingResult, TownPaths, TravelPath, TravelTarget};
+pub use trading::{HungerState, ItemName, TradeGood, TradingState};
+pub use travel::DistanceTravelled;
+pub use world_query::{CameraBounds, MapQuery};
+
+use crate::system::flow::AppState;
+use crate::world::spawning::PopulateWorldEvent;
+use crate::world::utils::ActiveLevel;
+
 pub struct WorldPlugin;
 impl Plugin for WorldPlugin {
 	fn build(&self, app: &mut App) {
@@ -42,11 +50,3 @@ impl Plugin for WorldPlugin {
 			);
 	}
 }
-
-pub use encounters::{EncounterState, WorldZones};
-pub use spawning::PendingLoadState;
-pub use towns::{CurrentResidence, PathingResult, TownPaths, TravelPath, TravelTarget};
-pub use trading::{HungerState, ItemName, TradeGood, TradingState};
-pub use world_query::{CameraBounds, MapQuery};
-
-use crate::world::spawning::PopulateWorldEvent;
diff --git a/game_core/src/world/spawning.rs b/game_core/src/world/spawning.rs
index ac86485..73240a8 100644
--- a/game_core/src/world/spawning.rs
+++ b/game_core/src/world/spawning.rs
@@ -10,6 +10,7 @@ use crate::states::Player;
 use crate::system::camera::ChaseCam;
 use crate::world::encounters::WorldZones;
 use crate::world::towns::{CurrentResidence, TownPaths};
+use crate::world::travel::DistanceTravelled;
 use crate::world::utils::{grid_to_px, px_to_grid, ActiveLevel, WorldLinked, TILE_SCALE_F32};
 use crate::world::world_query::MapQuery;
 use crate::world::{EncounterState, HungerState, TradingState, TravelPath, TravelTarget};
@@ -251,27 +252,37 @@ pub fn populate_world(
 					}
 				}
 			}
+
+			cmds.insert(
+				pending_load
+					.as_ref()
+					.map(|pending| pending.0.player_total_distance)
+					.unwrap_or_else(|| DistanceTravelled(0.0)),
+			);
+			cmds.insert(
+				pending_load
+					.as_ref()
+					.map(|pending| pending.0.player_inventory.clone())
+					.unwrap_or_else(|| TradingState {
+						gold: 500,
+						items: Default::default(),
+					}),
+			);
+			cmds.insert(
+				pending_load
+					.as_ref()
+					.map(|pending| pending.0.player_hunger.clone())
+					.unwrap_or_else(|| HungerState {
+						starvation_ticks: 0.0,
+						sustenance: 100,
+					}),
+			);
 		}
 
 		let world_zones = WorldZones::from_entities(level.px_hei, MapQuery::get_entities_of(level));
 
 		commands.insert_resource(trade_routes);
 		commands.insert_resource(world_zones);
-
-		commands.insert_resource(
-			pending_load
-				.as_ref()
-				.map(|pending| &pending.0.player_inventory)
-				.cloned()
-				.unwrap_or_default(),
-		);
-		commands.insert_resource(
-			pending_load
-				.as_ref()
-				.map(|pending| &pending.0.player_hunger)
-				.cloned()
-				.unwrap_or_default(),
-		);
 	}
 }
 
@@ -282,8 +293,6 @@ pub fn clean_game_state(
 ) {
 	commands.remove_resource::<PendingLoadState>();
 	commands.remove_resource::<PersistenceState>();
-	commands.remove_resource::<TradingState>();
-	commands.remove_resource::<HungerState>();
 
 	commands.insert_resource(TownPaths::default());
 	commands.insert_resource(WorldZones::default());
diff --git a/game_core/src/world/trading.rs b/game_core/src/world/trading.rs
index 212ebc2..6b70332 100644
--- a/game_core/src/world/trading.rs
+++ b/game_core/src/world/trading.rs
@@ -1,4 +1,4 @@
-use bevy::prelude::Resource;
+use bevy::prelude::{Component, Resource};
 use bevy::utils::hashbrown::hash_map::Entry;
 use bevy::utils::HashMap;
 use num_traits::AsPrimitive;
@@ -8,13 +8,13 @@ use crate::ui::components::IconContent;
 
 pub type ItemName = String;
 
-#[derive(Resource, Clone, Debug, Default, Serialize, Deserialize)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Component)]
 pub struct TradingState {
 	pub items: HashMap<ItemName, usize>,
 	pub gold: isize,
 }
 
-#[derive(Resource, Clone, Debug, Default, Serialize, Deserialize)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Component)]
 pub struct HungerState {
 	pub sustenance: usize,
 	pub starvation_ticks: f32,
diff --git a/game_core/src/world/travel.rs b/game_core/src/world/travel.rs
index 03a1e8b..a321936 100644
--- a/game_core/src/world/travel.rs
+++ b/game_core/src/world/travel.rs
@@ -2,12 +2,16 @@ use std::ops::SubAssign;
 
 use bevy::math::Vec3Swizzles;
 use bevy::prelude::*;
+use serde::{Deserialize, Serialize};
 
 use crate::persistance::SaveFileEvent;
 use crate::world::encounters::EncounterState;
 use crate::world::towns::{CurrentResidence, TravelPath, TravelTarget};
 use crate::world::PathingResult;
 
+#[derive(Component, Serialize, Deserialize, Debug, Copy, Clone, Default)]
+pub struct DistanceTravelled(pub f32);
+
 pub fn tick_travelling_merchant(
 	mut commands: Commands,
 	time: Res<Time>,
@@ -17,6 +21,7 @@ pub fn tick_travelling_merchant(
 		&mut TravelPath,
 		&mut TravelTarget,
 		&mut CurrentResidence,
+		&mut DistanceTravelled,
 	)>,
 	encounter_state: Res<EncounterState>,
 	mut autosave: EventWriter<SaveFileEvent>,
@@ -26,8 +31,11 @@ pub fn tick_travelling_merchant(
 	}
 
 	let delta = time.delta_seconds();
-	for (entity, mut transform, mut path, mut target, mut residence) in &mut merchant_query {
+	for (entity, mut transform, mut path, mut target, mut residence, mut high_score) in
+		&mut merchant_query
+	{
 		let step = path.get_step(transform.translation.xy(), 16.0) * delta;
+		high_score.0 += step.length();
 		transform.translation.sub_assign(step.extend(0.0));
 
 		match path.check_position(transform.translation.xy()) {
-- 
GitLab