1#![allow(clippy::type_complexity)]
2use std::collections::{HashMap, VecDeque};
3
4use bevy::{prelude::*, transform::TransformBundle};
5
6use crate::{
7 chunk::{Chunk, ChunkBundle},
8 tile::{Tile, TileBundle},
9 util::{chunk_from_location, tile_from_location},
10};
11
12#[derive(Clone, Debug)]
13pub enum TileEvent {
14 MakeChunk(IVec2),
15 SetTile {
16 loc: IVec2,
17 entity: Entity,
18 },
19 DeleteTile {
20 loc: IVec2,
21 mark: bool,
22 },
23 SetPixel {
24 loc: IVec2,
25 pixel: IVec2,
26 color: Color,
27 },
28}
29
30#[derive(Bundle, Default)]
31pub struct TilemapBundle {
32 tilemap: Tilemap,
33 transform: TransformBundle,
34 visibility: VisibilityBundle,
35}
36
37#[derive(Component, Default)]
38pub struct Tilemap {
39 chunks: HashMap<IVec2, Entity>,
40 pub(crate) tasks: VecDeque<TileEvent>,
41}
42
43impl Tilemap {
44 pub fn new() -> Self {
45 Self {
46 chunks: HashMap::new(),
47 tasks: VecDeque::new(),
48 }
49 }
50
51 pub fn require_chunk(&mut self, loc: IVec2) {
52 let chunk = chunk_from_location(loc);
53
54 if !self.chunks.contains_key(&IVec2::new(chunk.x, chunk.y)) {
55 self.tasks
56 .push_front(TileEvent::MakeChunk(IVec2::new(chunk.x, chunk.y)));
57 }
58 }
59
60 pub fn set_tile(
61 &mut self,
62 commands: &mut Commands,
63 loc: IVec2,
64 tile: Tile,
65 additional_components: impl Bundle,
66 ) -> Entity {
67 self.require_chunk(loc);
68
69 let entity = commands
70 .spawn((TileBundle::new(tile, loc), additional_components))
71 .id();
72
73 self.tasks.push_back(TileEvent::SetTile { loc, entity });
74
75 entity
76 }
77
78 pub fn try_set_tile(
79 &mut self,
80 commands: &mut Commands,
81 chunks: &Query<&Chunk>,
82 loc: IVec2,
83 tile: Tile,
84 additional_components: impl Bundle,
85 ) -> Option<Entity> {
86 if self.get_tile(loc, chunks).is_some() {
87 return None;
88 }
89
90 Some(self.set_tile(commands, loc, tile, additional_components))
91 }
92
93 pub fn set_pixel(&mut self, loc: IVec2, pixel: IVec2, color: Color) {
94 if !self.has_chunk(loc) {
95 return;
96 }
97
98 self.tasks
99 .push_back(TileEvent::SetPixel { loc, pixel, color })
100 }
101
102 pub fn delete_tile(&mut self, loc: IVec2) {
103 if !self.has_chunk(loc) {
104 return;
105 }
106
107 self.tasks
108 .push_back(TileEvent::DeleteTile { loc, mark: true });
109
110 self.require_chunk(loc)
111 }
112
113 pub fn delete_without_marker(&mut self, loc: IVec2) {
114 if !self.has_chunk(loc) {
115 return;
116 }
117
118 self.tasks
119 .push_back(TileEvent::DeleteTile { loc, mark: false });
120
121 self.require_chunk(loc)
122 }
123
124 pub fn get_tile(&mut self, loc: IVec2, chunks: &Query<&Chunk>) -> Option<Entity> {
125 if !self.has_chunk(loc) {
126 return None;
127 }
128
129 if let Some(entity) = self.get_chunk(loc) {
130 if let Ok(chunk) = chunks.get(entity) {
131 return chunk.get_tile(tile_from_location(loc));
132 }
133 }
134
135 None
136 }
137
138 pub fn get_chunk(&self, loc: IVec2) -> Option<Entity> {
139 let loc = chunk_from_location(loc);
140 self.chunks.get(&loc).copied()
141 }
142
143 pub fn has_chunk(&self, loc: IVec2) -> bool {
144 let loc = chunk_from_location(loc);
145 self.chunks.contains_key(&loc)
146 }
147}
148
149pub fn tilemap_event_system(
150 mut commands: Commands,
151 mut tilemaps: Query<(Entity, &mut Tilemap)>,
152 mut chunks: Query<(Entity, &mut Chunk)>,
153 mut tiles: Query<(Entity, &mut Tile)>,
154 mut images: ResMut<Assets<Image>>,
155) {
156 for (tilemap_entity, mut tilemap) in &mut tilemaps {
157 let mut remaining_tasks = VecDeque::new();
158 while let Some(event) = tilemap.tasks.pop_front() {
159 match event {
160 TileEvent::MakeChunk(loc) => {
161 if !tilemap.has_chunk(loc) {
162 let entity = commands
163 .spawn(ChunkBundle::new(
164 chunk_from_location(loc),
165 Chunk::new(&mut images),
166 ))
167 .id();
168
169 commands.entity(entity).set_parent(tilemap_entity);
170
171 tilemap.chunks.insert(chunk_from_location(loc), entity);
172 }
173 }
174 TileEvent::SetTile { loc, entity } => {
175 let chunk_loc = chunk_from_location(loc);
176
177 if !tilemap.has_chunk(loc) {
178 remaining_tasks.push_front(TileEvent::MakeChunk(loc));
179 remaining_tasks.push_back(TileEvent::SetTile { loc, entity });
180 } else if let Ok((chunk_entity, mut chunk)) =
181 chunks.get_mut(*tilemap.chunks.get(&chunk_loc).expect("chunk should exist"))
182 {
183 commands.entity(entity).set_parent(chunk_entity);
184 chunk.set_tile_entity(tile_from_location(loc), entity, &mut commands)
185 } else {
186 remaining_tasks.push_back(TileEvent::SetTile { loc, entity })
187 }
188 }
189 TileEvent::DeleteTile { loc, mark } => {
190 let chunk_loc = chunk_from_location(loc);
191
192 if tilemap.has_chunk(loc) {
193 if let Ok((_, mut chunk)) = chunks.get_mut(
194 *tilemap
195 .chunks
196 .get_mut(&chunk_loc)
197 .expect("Chunk should exist"),
198 ) {
199 if mark {
200 chunk.delete_tile(tile_from_location(loc), &mut commands);
201 } else {
202 chunk.delete_unmarked(tile_from_location(loc), &mut commands);
203 }
204 }
205 }
206 }
207 TileEvent::SetPixel { loc, pixel, color } => {
208 let chunk_loc = chunk_from_location(loc);
209
210 if tilemap.has_chunk(loc) {
211 if let Ok((_, mut chunk)) = chunks.get_mut(
212 *tilemap
213 .chunks
214 .get_mut(&chunk_loc)
215 .expect("Chunk should exist"),
216 ) {
217 if let Some(tile) = chunk.get_tile(tile_from_location(loc)) {
218 tiles
219 .get_mut(tile)
220 .expect("Tile should exist")
221 .1
222 .set_pixel(pixel, color);
223 }
224 chunk.update_tile(tile_from_location(loc));
225 }
226 }
227 }
228 }
229 }
230 tilemap.tasks.append(&mut remaining_tasks);
231 }
232}