1use std::collections::BTreeSet;
2
3use bevy::asset::io::Reader;
4use bevy::asset::{AssetLoader, LoadContext};
5use bevy::platform::collections::HashMap;
6use bevy::prelude::*;
7use bevy::reflect::TypePath;
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11use crate::editor::YoleckEditorState;
12use crate::entity_upgrading::YoleckEntityUpgrading;
13use crate::errors::YoleckAssetLoaderError;
14use crate::level_files_upgrading::upgrade_level_file;
15use crate::populating::PopulateReason;
16use crate::prelude::{YoleckEntityUuid, YoleckUuidRegistry};
17use crate::{
18 YoleckBelongsToLevel, YoleckEntityConstructionSpecs, YoleckEntityLifecycleStatus,
19 YoleckInternalSchedule, YoleckLevelJustLoaded, YoleckManaged, YoleckSchedule, YoleckState,
20};
21
22#[derive(Serialize, Deserialize, Debug, Clone)]
24pub struct YoleckEntryHeader {
25 #[serde(rename = "type")]
26 pub type_name: String,
27 #[serde(default)]
31 pub name: String,
32
33 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub uuid: Option<Uuid>,
39}
40
41#[derive(Component, Debug, Clone)]
43pub struct YoleckRawEntry {
44 pub header: YoleckEntryHeader,
45 pub data: serde_json::Map<String, serde_json::Value>,
46}
47
48impl Serialize for YoleckRawEntry {
49 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
50 where
51 S: serde::Serializer,
52 {
53 (&self.header, &self.data).serialize(serializer)
54 }
55}
56
57impl<'de> Deserialize<'de> for YoleckRawEntry {
58 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59 where
60 D: serde::Deserializer<'de>,
61 {
62 let (header, data) = Deserialize::deserialize(deserializer)?;
63 Ok(Self { header, data })
64 }
65}
66
67pub(crate) fn yoleck_process_raw_entries(
68 editor_state: Res<State<YoleckEditorState>>,
69 mut commands: Commands,
70 mut raw_entries_query: Query<(Entity, &mut YoleckRawEntry), With<YoleckBelongsToLevel>>,
71 construction_specs: Res<YoleckEntityConstructionSpecs>,
72 mut uuid_registry: ResMut<YoleckUuidRegistry>,
73) {
74 let mut entities_by_type = HashMap::<String, Vec<Entity>>::new();
75 for (entity, mut raw_entry) in raw_entries_query.iter_mut() {
76 entities_by_type
77 .entry(raw_entry.header.type_name.clone())
78 .or_default()
79 .push(entity);
80 let mut cmd = commands.entity(entity);
81 cmd.remove::<YoleckRawEntry>();
82
83 let mut components_data = HashMap::new();
84
85 if let Some(entity_type_info) =
86 construction_specs.get_entity_type_info(&raw_entry.header.type_name)
87 {
88 if entity_type_info.has_uuid {
89 let uuid = raw_entry.header.uuid.unwrap_or_else(Uuid::new_v4);
90 cmd.insert(YoleckEntityUuid(uuid));
91 uuid_registry.0.insert(uuid, cmd.id());
92 }
93 for component_name in entity_type_info.components.iter() {
94 let Some(handler) = construction_specs.component_handlers.get(component_name)
95 else {
96 error!("Component type {:?} is not registered", component_name);
97 continue;
98 };
99 let raw_component_data = raw_entry
100 .data
101 .get_mut(handler.key())
102 .map(|component_data| component_data.take());
103 handler.init_in_entity(raw_component_data, &mut cmd, &mut components_data);
104 }
105 for dlg in entity_type_info.on_init.iter() {
106 dlg(*editor_state.get(), &mut cmd);
107 }
108 } else {
109 error!("Entity type {:?} is not registered", raw_entry.header.name);
110 }
111
112 cmd.insert(YoleckManaged {
113 name: raw_entry.header.name.to_owned(),
114 type_name: raw_entry.header.type_name.to_owned(),
115 lifecycle_status: YoleckEntityLifecycleStatus::JustCreated,
116 components_data,
117 });
118 }
119}
120
121pub(crate) fn yoleck_prepare_populate_schedule(
122 mut query: Query<(Entity, &mut YoleckManaged)>,
123 mut entities_to_populate: ResMut<EntitiesToPopulate>,
124 mut yoleck_state: Option<ResMut<YoleckState>>,
125 editor_state: Res<State<YoleckEditorState>>,
126) {
127 entities_to_populate.0.clear();
128 let mut level_needs_saving = false;
129 for (entity, mut yoleck_managed) in query.iter_mut() {
130 match yoleck_managed.lifecycle_status {
131 YoleckEntityLifecycleStatus::Synchronized => {}
132 YoleckEntityLifecycleStatus::JustCreated => {
133 let populate_reason = match editor_state.get() {
134 YoleckEditorState::EditorActive => PopulateReason::EditorInit,
135 YoleckEditorState::GameActive => PopulateReason::RealGame,
136 };
137 entities_to_populate.0.push((entity, populate_reason));
138 }
139 YoleckEntityLifecycleStatus::JustChanged => {
140 entities_to_populate
141 .0
142 .push((entity, PopulateReason::EditorUpdate));
143 level_needs_saving = true;
144 }
145 }
146 yoleck_managed.lifecycle_status = YoleckEntityLifecycleStatus::Synchronized;
147 }
148 if level_needs_saving && let Some(yoleck_state) = yoleck_state.as_mut() {
149 yoleck_state.level_needs_saving = true;
150 }
151}
152
153pub(crate) fn yoleck_run_populate_schedule(world: &mut World) {
154 world.run_schedule(YoleckSchedule::Populate);
155 world.run_schedule(YoleckSchedule::OverrideCommonComponents);
156}
157
158#[derive(Resource)]
159pub(crate) struct EntitiesToPopulate(pub Vec<(Entity, PopulateReason)>);
160
161pub(crate) fn process_loading_command(
162 query: Query<(Entity, &YoleckLoadLevel)>,
163 mut raw_levels_assets: ResMut<Assets<YoleckRawLevel>>,
164 entity_upgrading: Option<Res<YoleckEntityUpgrading>>,
165 mut commands: Commands,
166) {
167 for (level_entity, load_level) in query.iter() {
168 if let Some(raw_level) = raw_levels_assets.get_mut(&load_level.0) {
169 if let Some(entity_upgrading) = &entity_upgrading {
170 entity_upgrading.upgrade_raw_level_file(raw_level);
171 }
172 commands
173 .entity(level_entity)
174 .remove::<YoleckLoadLevel>()
175 .insert((YoleckLevelJustLoaded, YoleckKeepLevel));
176 for entry in raw_level.entries() {
177 commands.spawn((
178 entry.clone(),
179 YoleckBelongsToLevel {
180 level: level_entity,
181 },
182 ));
183 }
184 }
185 }
186}
187
188pub(crate) fn yoleck_run_post_load_resolutions_schedule(world: &mut World) {
189 world.run_schedule(YoleckInternalSchedule::PostLoadResolutions);
190}
191
192pub(crate) fn yoleck_run_level_loaded_schedule(world: &mut World) {
193 world.run_schedule(YoleckSchedule::LevelLoaded);
194}
195
196pub(crate) fn yoleck_remove_just_loaded_marker_from_levels(
197 query: Query<Entity, With<YoleckLevelJustLoaded>>,
198 mut commands: Commands,
199) {
200 for level_entity in query.iter() {
201 commands
202 .entity(level_entity)
203 .remove::<YoleckLevelJustLoaded>();
204 }
205}
206
207pub(crate) fn process_unloading_command(
208 mut removed_levels: RemovedComponents<YoleckKeepLevel>,
209 level_owned_entities_query: Query<(Entity, &YoleckBelongsToLevel)>,
210 mut commands: Commands,
211) {
212 if removed_levels.is_empty() {
213 return;
214 }
215 let removed_levels: BTreeSet<Entity> = removed_levels.read().collect();
216 for (entity, belongs_to_level) in level_owned_entities_query.iter() {
217 if removed_levels.contains(&belongs_to_level.level) {
218 commands.entity(entity).despawn();
219 }
220 }
221}
222
223#[derive(Component)]
251pub struct YoleckLoadLevel(pub Handle<YoleckRawLevel>);
252
253#[derive(Component)]
260pub struct YoleckKeepLevel;
261
262#[derive(TypePath)]
263pub(crate) struct YoleckLevelAssetLoader;
264
265#[derive(Asset, TypePath, Debug, Serialize, Deserialize, Clone)]
267pub struct YoleckRawLevel(
268 pub(crate) YoleckRawLevelHeader,
269 serde_json::Value, pub(crate) Vec<YoleckRawEntry>,
271);
272
273#[derive(Debug, Serialize, Deserialize, Clone)]
275pub struct YoleckRawLevelHeader {
276 format_version: usize,
277 pub app_format_version: usize,
278}
279
280impl YoleckRawLevel {
281 pub fn new(
282 app_format_version: usize,
283 entries: impl IntoIterator<Item = YoleckRawEntry>,
284 ) -> Self {
285 Self(
286 YoleckRawLevelHeader {
287 format_version: 2,
288 app_format_version,
289 },
290 serde_json::Value::Object(Default::default()),
291 entries.into_iter().collect(),
292 )
293 }
294
295 pub fn entries(&self) -> &[YoleckRawEntry] {
296 &self.2
297 }
298
299 pub fn into_entries(self) -> impl Iterator<Item = YoleckRawEntry> {
300 self.2.into_iter()
301 }
302}
303
304impl AssetLoader for YoleckLevelAssetLoader {
305 type Asset = YoleckRawLevel;
306 type Settings = ();
307 type Error = YoleckAssetLoaderError;
308
309 fn extensions(&self) -> &[&str] {
310 &["yol"]
311 }
312
313 async fn load(
314 &self,
315 reader: &mut dyn Reader,
316 _settings: &Self::Settings,
317 _load_context: &mut LoadContext<'_>,
318 ) -> Result<Self::Asset, Self::Error> {
319 let mut bytes = Vec::new();
320 reader.read_to_end(&mut bytes).await?;
321 let json = std::str::from_utf8(&bytes)?;
322 let level: serde_json::Value = serde_json::from_str(json)?;
323 let level = upgrade_level_file(level)?;
324 let level: YoleckRawLevel = serde_json::from_value(level)?;
325 Ok(level)
326 }
327}