diff --git a/Cargo.lock b/Cargo.lock
index 578d3781f91bf453160bd6ec0a9702f0d6cb03e3..b44149f9d5a0922784aafe49e0dcf6a744767f98 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1330,6 +1330,26 @@ dependencies = [
  "parking_lot_core 0.9.5",
 ]
 
+[[package]]
+name = "directories"
+version = "4.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "discard"
 version = "1.0.4"
@@ -1601,6 +1621,7 @@ dependencies = [
  "bevy",
  "bevy_ecs_tilemap",
  "bevy_tweening",
+ "directories",
  "fake",
  "fastrand",
  "iyes_loopless",
@@ -3118,6 +3139,17 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom 0.2.8",
+ "redox_syscall",
+ "thiserror",
+]
+
 [[package]]
 name = "regex"
 version = "1.7.0"
diff --git a/game_core/Cargo.toml b/game_core/Cargo.toml
index 190e7f60ddaf758c2732b6da3141c087777ca5ad..0ce21e8922f2f69c0d4411c53e7a4d1863bfaad1 100644
--- a/game_core/Cargo.toml
+++ b/game_core/Cargo.toml
@@ -32,6 +32,7 @@ ldtk_rust = "0.6.0"
 kayak_ui.workspace = true
 kayak_font.workspace = true
 fake = "2.5.0"
+directories = "4.0.1"
 
 #remote_events = { git = "https://lab.lcr.gr/microhacks/micro-bevy-remote-events.git", rev = "be0c6b43a73e4c5e7ece20797e3d6f59340147b4"}
 
diff --git a/game_core/src/lib.rs b/game_core/src/lib.rs
index 97f8201f9a76fef42fbb3884b22251854ab5f70e..93c9644d3e6a73c914adc6bf0e543a3bd0d57789 100644
--- a/game_core/src/lib.rs
+++ b/game_core/src/lib.rs
@@ -1,6 +1,9 @@
+#![feature(result_option_inspect)]
+
 pub mod assets;
 pub mod graphics;
 pub mod multiplayer;
+pub mod persistance;
 pub mod splash_screen;
 pub mod states;
 pub mod system;
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index a8a988f7c1af0b495623a483e55028b59d76e517..3202daa3da84910b24465324d52dc9f2099b45c2 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -23,5 +23,6 @@ fn main() {
 		.add_plugin(game_core::graphics::GraphicsPlugin)
 		.add_plugins(micro_banimate::BanimatePluginGroup)
 		.add_plugins(AdventUIPlugins)
+		.add_plugin(game_core::persistance::PersistencePlugin)
 		.run();
 }
diff --git a/game_core/src/persistance/mod.rs b/game_core/src/persistance/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..07c7842479a2e42e88c159045f77049dc6c64c2e
--- /dev/null
+++ b/game_core/src/persistance/mod.rs
@@ -0,0 +1,35 @@
+mod save_file;
+
+mod __plugin {
+	use bevy::prelude::*;
+	use iyes_loopless::prelude::ConditionSet;
+
+	use crate::persistance::save_file::{handle_save_event, sync_state_to_persistence};
+	use crate::persistance::{LoadFileEvent, SaveFileEvent};
+	use crate::system::flow::AppState;
+
+	pub struct PersistencePlugin;
+	impl Plugin for PersistencePlugin {
+		fn build(&self, app: &mut App) {
+			app.add_event::<SaveFileEvent>()
+				.add_event::<LoadFileEvent>()
+				.add_system_set(
+					ConditionSet::new()
+						.run_in_state(AppState::InGame)
+						.with_system(sync_state_to_persistence)
+						.with_system(handle_save_event)
+						.with_system(
+							|input: Res<Input<KeyCode>>, mut events: EventWriter<SaveFileEvent>| {
+								if input.just_released(KeyCode::Space) {
+									events.send(SaveFileEvent { filename: None });
+								}
+							},
+						)
+						.into(),
+				);
+		}
+	}
+}
+
+pub use __plugin::PersistencePlugin;
+pub use save_file::{LoadFileEvent, PersistenceState, SaveFileEvent};
diff --git a/game_core/src/persistance/save_file.rs b/game_core/src/persistance/save_file.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a537eddece3aaf16fed6ecf56f7695aeb5cbcbf7
--- /dev/null
+++ b/game_core/src/persistance/save_file.rs
@@ -0,0 +1,118 @@
+use std::fs;
+use std::io::Write;
+use std::ops::Deref;
+use std::path::Path;
+
+use bevy::math::Vec3;
+use bevy::prelude::*;
+use bevy::utils::HashMap;
+use serde::{Deserialize, Serialize};
+
+use crate::states::Player;
+use crate::world::{
+	CurrentResidence, EncounterState, HungerState, TradingState, TravelPath, TravelTarget,
+};
+
+#[derive(Serialize, Deserialize, Debug, Resource)]
+pub struct PersistenceState {
+	pub player_location: Vec3,
+	pub player_inventory: TradingState,
+	pub player_hunger: HungerState,
+	pub travel_path: Option<TravelPath>,
+	pub travel_target: Option<TravelTarget>,
+	pub previous_location: CurrentResidence,
+	pub encounter_state: EncounterState,
+	pub town_states: HashMap<String, (TradingState, HungerState)>,
+}
+
+#[derive(Clone, Debug)]
+pub struct SaveFileEvent {
+	pub filename: Option<String>,
+}
+
+#[derive(Clone, Debug)]
+pub struct LoadFileEvent {
+	pub filename: Option<String>,
+}
+
+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>,
+	>,
+	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()
+			{
+				*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(),
+					travel_path: maybe_travel.cloned(),
+					travel_target: maybe_target.cloned(),
+					previous_location: residence.clone(),
+					encounter_state: encounters.clone(),
+					town_states: Default::default(),
+				};
+			}
+		}
+		None => {
+			if let Some((transform, maybe_travel, maybe_target, residence)) =
+				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(),
+					travel_path: maybe_travel.cloned(),
+					travel_target: maybe_target.cloned(),
+					previous_location: residence.clone(),
+					encounter_state: encounters.clone(),
+					town_states: Default::default(),
+				})
+			}
+		}
+	}
+}
+
+pub fn handle_save_event(
+	mut events: ResMut<Events<SaveFileEvent>>,
+	state: Option<Res<PersistenceState>>,
+) {
+	let root_data_dir = directories::ProjectDirs::from("com", "microhacks", "TraderTales")
+		.expect("Failed to get project dir");
+
+	if let Some(state) = state {
+		for event in events.drain() {
+			std::fs::create_dir_all(root_data_dir.data_dir()).expect("Failed to create data dir");
+
+			match fs::File::create(match event.filename {
+				Some(name) => root_data_dir.data_dir().join(name),
+				None => root_data_dir.data_dir().join("autosave.json"),
+			}) {
+				Ok(file) => {
+					serde_json::to_writer_pretty(file, &*state)
+						.inspect_err(|err| {
+							log::error!("{}", err);
+						})
+						.expect("Failed to create save data");
+				}
+				Err(e) => {
+					log::error!("{}", e);
+				}
+			}
+		}
+	}
+}
diff --git a/game_core/src/states/game_state.rs b/game_core/src/states/game_state.rs
index 6ffe00fa7b214c0c3bdc6769388de9b072664c09..25fc138656f6f5f12f14e08d9cb2941b297cb1cd 100644
--- a/game_core/src/states/game_state.rs
+++ b/game_core/src/states/game_state.rs
@@ -1,4 +1,16 @@
+use std::time::Duration;
+
 use bevy::prelude::Component;
+use micro_musicbox::prelude::{AudioEasing, AudioTween, MusicBox};
+
+use crate::assets::AssetHandles;
 
 #[derive(Component, Debug, Default, Copy, Clone)]
 pub struct Player;
+
+pub fn on_enter_game(mut musicbox: MusicBox<AssetHandles>) {
+	musicbox.fade_in_music(
+		"bgm",
+		AudioTween::new(Duration::from_secs(2), AudioEasing::Linear),
+	);
+}
diff --git a/game_core/src/states/menu_state.rs b/game_core/src/states/menu_state.rs
index f6a1012c3f3826a07d6557148d3ccdb55c3fb56e..bc37c7906051a2b225e2db43dbc84da779f1c51b 100644
--- a/game_core/src/states/menu_state.rs
+++ b/game_core/src/states/menu_state.rs
@@ -17,11 +17,6 @@ pub fn spawn_menu_entities(
 	assets: Res<AssetHandles>,
 	mut musicbox: MusicBox<AssetHandles>,
 ) {
-	musicbox.fade_in_music(
-		"bgm",
-		AudioTween::new(Duration::from_secs(2), AudioEasing::Linear),
-	);
-
 	commands.spawn((
 		SpriteBundle {
 			texture: assets.image("menu_background"),
diff --git a/game_core/src/states/mod.rs b/game_core/src/states/mod.rs
index 01a9f906629e6a20cfc43a3491d57e0ab1c8803d..5a5214ce80f59f6306a8029b34b3bce165255e40 100644
--- a/game_core/src/states/mod.rs
+++ b/game_core/src/states/mod.rs
@@ -12,6 +12,7 @@ impl Plugin for StatesPlugin {
 	fn build(&self, app: &mut App) {
 		app.add_enter_system(AppState::Menu, menu_state::spawn_menu_entities)
 			.add_exit_system(AppState::Menu, menu_state::despawn_menu_entities)
+			.add_enter_system(AppState::InGame, game_state::on_enter_game)
 			.add_system_set(
 				ConditionSet::new()
 					.run_in_state(AppState::Menu)
diff --git a/game_core/src/ui/components/inset_icon.rs b/game_core/src/ui/components/inset_icon.rs
index ac4b68c49b0c819b37e3b2fb99472c7192a12391..89814c20538f55b165fd44f2508194ffad1924fc 100644
--- a/game_core/src/ui/components/inset_icon.rs
+++ b/game_core/src/ui/components/inset_icon.rs
@@ -1,12 +1,13 @@
 use bevy::prelude::*;
 use kayak_ui::prelude::*;
 use kayak_ui::widgets::{KImage, KImageBundle, TextureAtlasBundle, TextureAtlasProps};
+use serde::{Deserialize, Serialize};
 
 use crate::assets::AssetHandles;
 use crate::parent_widget;
 use crate::ui::prelude::*;
 
-#[derive(Clone, Default, Eq, PartialEq)]
+#[derive(Clone, Default, Debug, Eq, PartialEq, Serialize, Deserialize)]
 pub enum IconContent {
 	Image(String),
 	Atlas(String, usize),
diff --git a/game_core/src/world/encounters.rs b/game_core/src/world/encounters.rs
index ac3e67dfb24815c4c6924447161801d609573977..4964ff5591db679de8a0536085d98d181ff8f1c9 100644
--- a/game_core/src/world/encounters.rs
+++ b/game_core/src/world/encounters.rs
@@ -5,6 +5,7 @@ use fake::faker::lorem::en::{Paragraph, Paragraphs};
 use fake::Fake;
 use ldtk_rust::{EntityInstance, FieldInstance};
 use num_traits::AsPrimitive;
+use serde::{Deserialize, Serialize};
 use serde_json::Value;
 
 use crate::states::Player;
@@ -114,7 +115,7 @@ impl From<Vec<EncounterZone>> for WorldZones {
 	}
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum EncounterOutcome {
 	GainResource {
 		resource_type: String,
@@ -133,13 +134,13 @@ pub enum EncounterOutcome {
 	},
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct EncounterOption {
 	pub label: String,
 	pub outcome: Vec<EncounterOutcome>,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct Encounter {
 	pub title: String,
 	pub description: String,
@@ -185,7 +186,7 @@ pub fn gen_encounter() -> Encounter {
 	}
 }
 
-#[derive(Clone, Default, Resource, Debug)]
+#[derive(Clone, Default, Resource, Debug, Serialize, Deserialize)]
 pub enum EncounterState {
 	#[default]
 	NoEncounter,
diff --git a/game_core/src/world/mod.rs b/game_core/src/world/mod.rs
index 96ca28d423581da9ed8473e97717b298e4f2a56d..addfc269f2905ef6cdc81ddf1c8e8fbfd7f047c6 100644
--- a/game_core/src/world/mod.rs
+++ b/game_core/src/world/mod.rs
@@ -10,6 +10,7 @@ mod encounters;
 mod generators;
 mod spawning;
 mod towns;
+mod trading;
 mod travel;
 mod utils;
 mod world_query;
@@ -20,6 +21,7 @@ impl Plugin for WorldPlugin {
 		app.init_resource::<TownPaths>()
 			.init_resource::<WorldZones>()
 			.init_resource::<EncounterState>()
+			.add_event::<PopulateWorldEvent>()
 			.add_enter_system(AppState::InGame, |mut commands: Commands| {
 				commands.insert_resource(ActiveLevel::new("Grantswaith"));
 			})
@@ -28,6 +30,7 @@ impl Plugin for WorldPlugin {
 					.run_in_state(AppState::InGame)
 					// .with_system(debug::create_tombstones)
 					.with_system(spawning::spawn_world_data)
+					.with_system(spawning::populate_world)
 					.with_system(travel::tick_travelling_merchant)
 					.with_system(encounters::notify_new_zone)
 
@@ -38,4 +41,7 @@ impl Plugin for WorldPlugin {
 
 pub use encounters::{EncounterState, WorldZones};
 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 ce1448848c8b2b203d3b81b7fccd8d5ec302d104..8ffe127fab49669d0602f561336502b26e351032 100644
--- a/game_core/src/world/spawning.rs
+++ b/game_core/src/world/spawning.rs
@@ -5,12 +5,17 @@ use num_traits::AsPrimitive;
 use serde_json::Value;
 
 use crate::assets::{AssetHandles, LdtkProject, LevelIndex, TilesetIndex};
+use crate::persistance::PersistenceState;
 use crate::states::Player;
 use crate::system::camera::ChaseCam;
 use crate::world::encounters::WorldZones;
 use crate::world::towns::{CurrentResidence, TownPaths};
 use crate::world::utils::{grid_to_px, px_to_grid, ActiveLevel, WorldLinked, TILE_SCALE_F32};
 use crate::world::world_query::MapQuery;
+use crate::world::{TravelPath, TravelTarget};
+
+#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
+pub struct PopulateWorldEvent;
 
 pub fn spawn_world_data(
 	mut commands: Commands,
@@ -20,6 +25,7 @@ pub fn spawn_world_data(
 	level_index: Res<LevelIndex>,
 	tileset_index: Res<TilesetIndex>,
 	mut last_spawned_level: Local<String>,
+	mut events: EventWriter<PopulateWorldEvent>,
 ) {
 	let mut active_level = match active_level {
 		Some(l) => l,
@@ -149,41 +155,116 @@ pub fn spawn_world_data(
 			));
 		});
 
+		events.send(PopulateWorldEvent);
+	}
+}
+
+#[derive(Resource)]
+pub struct PendingLoadState(PersistenceState);
+
+pub fn populate_world(
+	mut commands: Commands,
+	mut events: ResMut<Events<PopulateWorldEvent>>,
+	mut active_level: Option<ResMut<ActiveLevel>>,
+	assets: Res<AssetHandles>,
+	level_index: Res<LevelIndex>,
+	existing_player: Query<Entity, With<Player>>,
+	pending_load: Option<Res<PendingLoadState>>,
+) {
+	let should_populate = !events
+		.drain()
+		.collect::<Vec<PopulateWorldEvent>>()
+		.is_empty();
+
+	if should_populate {
+		let mut active_level = match active_level {
+			Some(l) => l,
+			None => return,
+		};
+
+		let level = match level_index.get(&active_level.map) {
+			Some(l) => l,
+			None => return,
+		};
+
 		let trade_routes = TownPaths::from(MapQuery::get_entities_of(level));
-		let random_start = trade_routes.routes.get(&String::from("The Royal Lampoon"));
-
-		if let Some(start) = random_start {
-			if let Some(route) = start.routes.values().next() {
-				let point = route.nodes[0];
-
-				commands.spawn((
-					SpriteSheetBundle {
-						transform: Transform::from_xyz(
-							grid_to_px(point.tile_x),
-							level.px_hei as f32 - grid_to_px(point.tile_y),
-							400.0,
-						),
-						texture_atlas: assets.atlas("icons"),
-						sprite: TextureAtlasSprite {
-							index: 0,
-							..Default::default()
-						},
+
+		let start = trade_routes
+			.routes
+			.get(&String::from("The Royal Lampoon"))
+			.unwrap();
+
+		if let Some(route) = start.routes.values().next() {
+			let point = route.nodes[0];
+
+			let mut cmds = commands.spawn((
+				SpriteSheetBundle {
+					transform: pending_load
+						.as_ref()
+						.map(|pending| Transform::from_translation(pending.0.player_location))
+						.unwrap_or_else(|| {
+							Transform::from_xyz(
+								grid_to_px(point.tile_x),
+								level.px_hei as f32 - grid_to_px(point.tile_y),
+								400.0,
+							)
+						}),
+					texture_atlas: assets.atlas("icons"),
+					sprite: TextureAtlasSprite {
+						index: 0,
 						..Default::default()
 					},
-					start
-						.create_route_bundle_for(
-							start
-								.routes
-								.keys()
-								.nth(fastrand::usize(0..start.routes.len()))
-								.cloned()
-								.unwrap(),
-							level,
-						)
-						.unwrap(),
-					Player,
-					ChaseCam,
-				));
+					..Default::default()
+				},
+				start
+					.create_route_bundle_for(
+						start
+							.routes
+							.keys()
+							.nth(fastrand::usize(0..start.routes.len()))
+							.cloned()
+							.unwrap(),
+						level,
+					)
+					.unwrap(),
+				Player,
+				ChaseCam,
+			));
+
+			match &pending_load {
+				Some(ref state) => {
+					match (
+						&state.0.previous_location,
+						&state.0.travel_path,
+						&state.0.travel_target,
+					) {
+						(res, Some(path), Some(target)) => {
+							cmds.insert((res.clone(), path.clone(), target.clone()));
+						}
+						(res, None, Some(target)) => {
+							cmds.insert((res.clone(), target.clone()));
+						}
+						(res, Some(path), None) => {
+							cmds.insert((res.clone(), path.clone()));
+						}
+						(res, None, None) => {
+							cmds.insert(res.clone());
+						}
+					}
+				}
+				None => {
+					if let Some(bundle) = start.create_route_bundle_for(
+						start
+							.routes
+							.keys()
+							.nth(fastrand::usize(0..start.routes.len()))
+							.cloned()
+							.unwrap(),
+						level,
+					) {
+						cmds.insert(bundle);
+					}
+				}
 			}
 		}
 
@@ -191,5 +272,20 @@ pub fn spawn_world_data(
 
 		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(),
+		);
 	}
 }
diff --git a/game_core/src/world/towns.rs b/game_core/src/world/towns.rs
index 67705f35b18fceac8cce7d5a81860f15df67a5f7..a2a0cb8bb375d48839a8d2a0194f2e37287e222a 100644
--- a/game_core/src/world/towns.rs
+++ b/game_core/src/world/towns.rs
@@ -11,7 +11,7 @@ pub type Town = String;
 
 /// Stores the ID of the most recent town that has been inhabited,
 /// as well as the current travel state
-#[derive(Component)]
+#[derive(Component, Serialize, Deserialize, Debug, Clone)]
 pub enum CurrentResidence {
 	TravellingFrom(String),
 	RestingAt(String),
@@ -31,10 +31,10 @@ impl CurrentResidence {
 	}
 }
 
-#[derive(Component)]
+#[derive(Component, Serialize, Deserialize, Debug, Clone)]
 pub struct TravelTarget(pub String);
 
-#[derive(Component)]
+#[derive(Component, Serialize, Deserialize, Debug, Clone)]
 pub struct TravelPath {
 	pub path: Vec<Vec2>,
 	last_index: usize,
diff --git a/game_core/src/world/trading.rs b/game_core/src/world/trading.rs
new file mode 100644
index 0000000000000000000000000000000000000000..212ebc20cdef8844a7459cb629303a2a461da87d
--- /dev/null
+++ b/game_core/src/world/trading.rs
@@ -0,0 +1,96 @@
+use bevy::prelude::Resource;
+use bevy::utils::hashbrown::hash_map::Entry;
+use bevy::utils::HashMap;
+use num_traits::AsPrimitive;
+use serde::{Deserialize, Serialize};
+
+use crate::ui::components::IconContent;
+
+pub type ItemName = String;
+
+#[derive(Resource, Clone, Debug, Default, Serialize, Deserialize)]
+pub struct TradingState {
+	pub items: HashMap<ItemName, usize>,
+	pub gold: isize,
+}
+
+#[derive(Resource, Clone, Debug, Default, Serialize, Deserialize)]
+pub struct HungerState {
+	pub sustenance: usize,
+	pub starvation_ticks: f32,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize)]
+pub struct TradeGood {
+	pub name: String,
+	pub icon: IconContent,
+}
+
+impl TradingState {
+	pub fn spend_gold(&mut self, amount: impl AsPrimitive<isize>) -> bool {
+		if self.gold < amount.as_() {
+			false
+		} else {
+			self.adjust_gold(amount);
+			true
+		}
+	}
+
+	pub fn adjust_gold(&mut self, adjustment: impl AsPrimitive<isize>) {
+		self.gold += adjustment.as_();
+	}
+
+	pub fn remove_items(
+		&mut self,
+		identifier: impl ToString,
+		amount: impl AsPrimitive<usize>,
+	) -> bool {
+		match self.items.entry(identifier.to_string()) {
+			Entry::Occupied(mut e) => {
+				let amount = amount.as_();
+				if e.get() >= &amount {
+					*e.get_mut() -= amount;
+					if e.get() == &0 {
+						e.remove();
+					}
+					true
+				} else {
+					false
+				}
+			}
+			Entry::Vacant(_) => false,
+		}
+	}
+
+	pub fn add_items(&mut self, identifier: impl ToString, amount: impl AsPrimitive<usize>) {
+		*self.items.entry(identifier.to_string()).or_insert(0) += amount.as_()
+	}
+
+	pub fn try_buy_items(
+		&mut self,
+		cost: impl AsPrimitive<isize>,
+		identifier: impl ToString,
+		amount: impl AsPrimitive<usize>,
+	) -> bool {
+		if self.spend_gold(cost) {
+			self.add_items(identifier, amount);
+			true
+		} else {
+			false
+		}
+	}
+
+	pub fn try_sell_items(
+		&mut self,
+		value: impl AsPrimitive<isize>,
+		identifier: impl ToString,
+		amount: impl AsPrimitive<usize>,
+	) -> bool {
+		if self.remove_items(identifier, amount) {
+			self.adjust_gold(value);
+			true
+		} else {
+			false
+		}
+	}
+}