src/items.rs 3.6 K · 155 lines · raw · history

1 use bevy::prelude::*;
2
3 use crate::{
4 world::{TILE_SIZE, TilePos, WorldMap},
5 worldgen::WorldGenerated,
6 };
7
8 pub struct ItemsPlugin;
9
10 impl Plugin for ItemsPlugin {
11 fn build(&self, app: &mut App) {
12 app.add_systems(Startup, create_item_assets)
13 .add_systems(Update, clear_items.run_if(on_message::<WorldGenerated>));
14 }
15 }
16
17 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18 pub enum ItemKind {
19 Wood,
20 }
21
22 impl ItemKind {
23 pub const ALL: [ItemKind; 1] = [ItemKind::Wood];
24
25 fn color(self) -> Color {
26 match self {
27 ItemKind::Wood => Color::srgb_u8(150, 104, 62),
28 }
29 }
30 }
31
32 #[derive(Component, Debug, Clone, Copy, PartialEq)]
33 #[require(Transform)]
34 pub struct Item {
35 pub kind: ItemKind,
36 pub count: u32,
37 }
38
39 #[derive(Resource, Default)]
40 pub struct ItemAssets {
41 mesh: Handle<Mesh>,
42 materials: Vec<(ItemKind, Handle<ColorMaterial>)>,
43 }
44
45 impl ItemAssets {
46 fn material(&self, kind: ItemKind) -> Handle<ColorMaterial> {
47 self.materials
48 .iter()
49 .find(|(k, _)| *k == kind)
50 .map(|(_, m)| m.clone())
51 .unwrap_or_default()
52 }
53 }
54
55 fn create_item_assets(
56 mut commands: Commands,
57 mut meshes: ResMut<Assets<Mesh>>,
58 mut materials: ResMut<Assets<ColorMaterial>>,
59 ) {
60 commands.insert_resource(ItemAssets {
61 mesh: meshes.add(Rectangle::new(TILE_SIZE * 0.7, TILE_SIZE * 0.4)),
62 materials: ItemKind::ALL
63 .iter()
64 .map(|&kind| (kind, materials.add(kind.color())))
65 .collect(),
66 });
67 }
68
69 fn clear_items(mut commands: Commands, items: Query<Entity, With<Item>>) {
70 for entity in &items {
71 commands.entity(entity).despawn();
72 }
73 }
74
75 pub fn drop_items(
76 commands: &mut Commands,
77 items: &mut Query<(&mut Item, &TilePos)>,
78 assets: &ItemAssets,
79 tile: UVec2,
80 kind: ItemKind,
81 count: u32,
82 ) {
83 if let Some((mut item, _)) = items
84 .iter_mut()
85 .find(|(item, pos)| pos.0 == tile && item.kind == kind)
86 {
87 item.count += count;
88 return;
89 }
90 commands.spawn((
91 Name::new(format!("{kind:?}")),
92 Item { kind, count },
93 TilePos(tile),
94 Mesh2d(assets.mesh.clone()),
95 MeshMaterial2d(assets.material(kind)),
96 Transform::from_translation(WorldMap::tile_center(tile).extend(0.7)),
97 ));
98 }
99
100 pub fn stock<'a>(items: impl Iterator<Item = &'a Item>, kind: ItemKind) -> u32 {
101 items.filter(|i| i.kind == kind).map(|i| i.count).sum()
102 }
103
104 #[cfg(test)]
105 mod tests {
106 use super::*;
107
108 fn drop_system(mut commands: Commands, mut items: Query<(&mut Item, &TilePos)>) {
109 let assets = ItemAssets::default();
110 drop_items(
111 &mut commands,
112 &mut items,
113 &assets,
114 UVec2::ONE,
115 ItemKind::Wood,
116 5,
117 );
118 }
119
120 #[test]
121 fn dropping_on_a_tile_with_the_same_item_stacks() {
122 let mut app = App::new();
123 app.add_systems(Update, drop_system);
124 app.update();
125 app.update();
126 let items: Vec<Item> = app
127 .world_mut()
128 .query::<&Item>()
129 .iter(app.world())
130 .copied()
131 .collect();
132 assert_eq!(
133 items,
134 vec![Item {
135 kind: ItemKind::Wood,
136 count: 10
137 }]
138 );
139 }
140
141 #[test]
142 fn stock_sums_counts_of_one_kind() {
143 let items = [
144 Item {
145 kind: ItemKind::Wood,
146 count: 3,
147 },
148 Item {
149 kind: ItemKind::Wood,
150 count: 4,
151 },
152 ];
153 assert_eq!(stock(items.iter(), ItemKind::Wood), 7);
154 }
155 }