1use crate::editor::{CONFIGEDITOR, PALETTE, SCENEMANAGER};
2use crate::prelude::*;
3use codegridfx::{Module, ModuleType};
4use rusteria::{RenderBuffer, Rusteria};
5use rusterix::{PixelSource, Value, ValueContainer, pixel_to_vec4};
6use std::str::FromStr;
7use toml::*;
8
9pub fn default_preview_rigging_toml() -> String {
10 if let Some(bytes) = crate::Embedded::get("toml/preview_rigging.toml")
11 && let Ok(source) = std::str::from_utf8(bytes.data.as_ref())
12 {
13 return source.to_string();
14 }
15 "# Editor-only preview. Runtime equipment/animation comes from server scripts.\nanimation = \"Idle\"\nperspective = \"Front\"\nplay = true\nspeed = 1.0\n\n[slots]\n# main_hand = \"Item Name\"\n# off_hand = \"Item Name\"\n".to_string()
16}
17
18pub fn update_region_settings(
19 project: &mut Project,
20 server_ctx: &ServerContext,
21) -> Result<bool, Box<dyn std::error::Error>> {
22 let mut changed = false;
23 if let Some(id) = server_ctx.pc.id() {
24 if server_ctx.pc.is_region() {
25 if let Some(region) = project.get_region_mut(&id) {
26 let parsed: toml::Value = toml::from_str(®ion.config)?;
27 if let Some(section) = parsed.get("terrain") {
29 if let Some(enabled) = section.get("enabled") {
30 let enabled = enabled.as_bool().unwrap_or(false);
31 region
37 .map
38 .properties
39 .set("terrain_enabled", Value::Bool(enabled));
40 changed = true;
41 }
43
44 let mut got_tile_id = false;
45 if let Some(enabled) = section.get("tile_id") {
46 if let Some(tile_id) = enabled.as_str() {
47 if let Ok(id) = Uuid::from_str(tile_id) {
48 region.map.properties.set(
49 "default_terrain_tile",
50 Value::Source(PixelSource::TileId(id)),
51 );
52 got_tile_id = true;
53 }
54 }
55 }
56 if !got_tile_id {
57 region.map.properties.remove("default_terrain_tile");
58 }
59 }
60
61 if let Some(section) = parsed.get("preview").and_then(toml::Value::as_table) {
62 region.map.properties.remove("preview_hide");
63 if let Some(values) = section.get("hide").and_then(toml::Value::as_array) {
64 let hide: Vec<String> = values
65 .iter()
66 .filter_map(toml::Value::as_str)
67 .map(str::trim)
68 .filter(|s| !s.is_empty())
69 .map(str::to_string)
70 .collect();
71 if !hide.is_empty() {
72 region
73 .map
74 .properties
75 .set("preview_hide", Value::StrArray(hide));
76 }
77 }
78 changed = true;
79 } else {
80 region.map.properties.remove("preview_hide");
81 }
82 }
83 }
84 }
85
86 Ok(changed)
87}
88
89pub fn set_code(
91 ui: &mut TheUI,
92 ctx: &mut TheContext,
93 project: &mut Project,
94 server_ctx: &ServerContext,
95) {
96 let mut success = false;
97
98 match server_ctx.cc {
99 ContentContext::CharacterInstance(uuid) => {
100 if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
101 if let Some(character_instance) = region.characters.get_mut(&uuid) {
102 ui.set_widget_value(
103 "CodeEdit",
104 ctx,
105 TheValue::Text(character_instance.source.clone()),
106 );
107 success = true;
108 }
109 }
110 }
111 ContentContext::Sector(uuid) => {
112 if let Some(map) = project.get_map_mut(server_ctx) {
113 for s in &map.sectors {
114 if s.creator_id == uuid {
115 if let Some(Value::Str(source)) = s.properties.get("source") {
116 ui.set_widget_value("CodeEdit", ctx, TheValue::Text(source.clone()));
117 success = true;
118 }
119 if let Some(Value::Str(data)) = s.properties.get("data") {
120 ui.set_widget_value("DataEdit", ctx, TheValue::Text(data.clone()));
121 success = true;
122 }
123 break;
124 }
125 }
126 }
127 }
128 ContentContext::CharacterTemplate(uuid) => {
129 if let Some(character) = project.characters.get_mut(&uuid) {
130 ui.set_widget_value("CodeEdit", ctx, TheValue::Text(character.source.clone()));
131 ui.set_widget_value("DataEdit", ctx, TheValue::Text(character.data.clone()));
132 character
133 .module
134 .set_module_type(ModuleType::CharacterTemplate);
135 success = true;
136 }
137 }
138 ContentContext::ItemTemplate(uuid) => {
139 if let Some(item) = project.items.get_mut(&uuid) {
140 ui.set_widget_value("CodeEdit", ctx, TheValue::Text(item.source.clone()));
141 ui.set_widget_value("DataEdit", ctx, TheValue::Text(item.data.clone()));
142 item.module.set_module_type(ModuleType::ItemTemplate);
143 success = true;
144 }
145 }
146 ContentContext::ItemInstance(uuid) => {
147 let mut temp_id = None;
148
149 if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
150 if let Some(item_inst) = region.items.get_mut(&uuid) {
151 temp_id = Some(item_inst.item_id);
152 }
153 }
154
155 if let Some(temp_id) = temp_id {
156 if let Some(item) = project.items.get_mut(&temp_id) {
157 ui.set_widget_value("CodeEdit", ctx, TheValue::Text(item.source.clone()));
158 ui.set_widget_value("DataEdit", ctx, TheValue::Text(item.data.clone()));
159 item.module.set_module_type(ModuleType::ItemTemplate);
160 success = true;
161 }
162 }
163 }
164 _ => {}
165 }
166
167 if !success {
200 ui.set_widget_value("CodeEdit", ctx, TheValue::Text(String::new()));
201 }
202}
203
204pub fn get_source(_ui: &mut TheUI, server_ctx: &ServerContext) -> Option<PixelSource> {
206 let mut source: Option<PixelSource> = None;
207
208 if let Some(id) = server_ctx.curr_tile_id {
209 source = Some(PixelSource::TileId(id));
210 }
211
212 source
213}
214
215pub fn extract_build_values_from_config(values: &mut ValueContainer) {
216 let config = CONFIGEDITOR.read().unwrap();
217 let sample_mode = config.get_string_default("render", "sample_mode", "nearest");
218 if sample_mode == "linear" {
219 values.set(
220 "sample_mode",
221 Value::SampleMode(rusterix::SampleMode::Linear),
222 );
223 } else {
224 values.set(
225 "sample_mode",
226 Value::SampleMode(rusterix::SampleMode::Nearest),
227 );
228 }
229}
230
231pub fn apply_region_config(map: &mut Map, config: String) {
233 map.properties.remove("preview_hide");
235
236 if let Ok(table) = config.parse::<Table>() {
237 if let Some(rendering) = table.get("rendering").and_then(toml::Value::as_table) {
238 if let Some(value) = rendering.get("receives_daylight") {
240 if let Some(v) = value.as_bool() {
241 map.properties.set("receives_daylight", Value::Bool(v));
242 }
243 }
244
245 if let Some(value) = rendering.get("fog_enabled") {
247 if let Some(v) = value.as_bool() {
248 map.properties.set("fog_enabled", Value::Bool(v));
249 }
250 }
251 if let Some(value) = rendering.get("fog_start_distance") {
252 if let Some(v) = value.as_float() {
253 map.properties
254 .set("fog_start_distance", Value::Float(v as f32));
255 }
256 }
257 if let Some(value) = rendering.get("fog_end_distance") {
258 if let Some(v) = value.as_float() {
259 map.properties
260 .set("fog_end_distance", Value::Float(v as f32));
261 }
262 }
263 let mut fog_color = Vec4::zero();
264 if let Some(value) = rendering.get("fog_color") {
265 if let Some(v) = value.as_str() {
266 let c = hex_to_rgba_u8(v);
267 fog_color = pixel_to_vec4(&c);
268 }
269 }
270 map.properties.set(
271 "fog_color",
272 Value::Vec4([fog_color.x, fog_color.y, fog_color.z, fog_color.w]),
273 );
274 }
275
276 if let Some(preview) = table.get("preview").and_then(toml::Value::as_table)
277 && let Some(hide_values) = preview.get("hide").and_then(toml::Value::as_array)
278 {
279 let hide: Vec<String> = hide_values
280 .iter()
281 .filter_map(toml::Value::as_str)
282 .map(str::to_string)
283 .filter(|s| !s.is_empty())
284 .collect();
285 if !hide.is_empty() {
286 map.properties.set("preview_hide", Value::StrArray(hide));
287 }
288 }
289 }
290}
291
292pub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {
294 let hex = hex.trim_start_matches('#');
295
296 match hex.len() {
297 6 => match (
298 u8::from_str_radix(&hex[0..2], 16),
299 u8::from_str_radix(&hex[2..4], 16),
300 u8::from_str_radix(&hex[4..6], 16),
301 ) {
302 (Ok(r), Ok(g), Ok(b)) => [r, g, b, 255],
303 _ => [255, 255, 255, 255],
304 },
305 8 => match (
306 u8::from_str_radix(&hex[0..2], 16),
307 u8::from_str_radix(&hex[2..4], 16),
308 u8::from_str_radix(&hex[4..6], 16),
309 u8::from_str_radix(&hex[6..8], 16),
310 ) {
311 (Ok(r), Ok(g), Ok(b), Ok(a)) => [r, g, b, a],
312 _ => [255, 255, 255, 255],
313 },
314 _ => [255, 255, 255, 255],
315 }
316}
317
318pub fn is_valid_python_variable(name: &str) -> bool {
320 let mut chars = name.chars();
322 match chars.next() {
323 Some(c) if c.is_ascii_alphabetic() || c == '_' => (),
324 _ => return false,
325 }
326 if name.is_empty() {
327 return false;
328 }
329 if name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
330 const PYTHON_KEYWORDS: &[&str] = &[
332 "False", "None", "True", "and", "as", "assert", "break", "class", "continue", "def",
333 "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import",
334 "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try",
335 "while", "with", "yield",
336 ];
337 !PYTHON_KEYWORDS.contains(&name)
338 } else {
339 false
340 }
341}
342
343pub fn draw_shader_into(module: &Module, buffer: &mut TheRGBABuffer) {
345 use std::sync::{Arc, Mutex};
346
347 let mut rs = Rusteria::default();
348 let code = module.build_shader();
349
350 let _module = match rs.parse_str(&code) {
351 Ok(module) => match rs.compile(&module) {
352 Ok(()) => {
353 println!("Module '{}' compiled successfully.", module.name);
354 }
355 Err(e) => {
356 eprintln!("Error compiling module: {e}");
357 return;
358 }
359 },
360 Err(e) => {
361 eprintln!("Error parsing module: {e}");
362 return;
363 }
364 };
365
366 let width = buffer.dim().width as usize;
367 let height = buffer.dim().height as usize;
368
369 if let Some(shade_index) = rs.context.program.shade_index {
370 let mut rbuffer = Arc::new(Mutex::new(RenderBuffer::new(width, height)));
371 let t0 = rs.get_time();
372 rs.shade(&mut rbuffer, shade_index, &PALETTE.read().unwrap());
373 let t1 = rs.get_time();
374 println!("Rendered in {}ms", t1 - t0);
375
376 let b = rbuffer.lock().unwrap().as_rgba_bytes();
377 *buffer = TheRGBABuffer::from(b, width as u32, height as u32)
378 }
379}
380
381pub fn scenemanager_render_map(project: &Project, server_ctx: &ServerContext) {
383 if server_ctx.get_map_context() == MapContext::Screen {
385 return;
386 }
387
388 if server_ctx.editor_view_mode == EditorViewMode::D2 {
389 if let Some(map) = project.get_map(server_ctx) {
391 SCENEMANAGER.write().unwrap().set_map(map.clone());
392 }
393 } else {
394 if server_ctx.get_map_context() != MapContext::Region {
397 return;
398 }
399
400 if let Some(id) = server_ctx.pc.id() {
402 if let Some(region) = project.get_region(&id) {
403 let mut map = region.map.clone();
404 apply_region_config(&mut map, region.config.clone());
406 SCENEMANAGER.write().unwrap().set_map(map);
407 }
408 }
409 }
410}