1use gizmo_core::input::Input;
2use gizmo_core::World;
3use mlua::prelude::*;
4use mlua::RegistryKey;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8use crate::api_ai;
9use crate::api_audio;
10use crate::api_entity;
11use crate::api_input;
12use crate::api_physics;
13use crate::api_scene;
14use crate::api_time;
15use crate::api_vehicle;
16use crate::commands::{CommandQueue, ScriptCommand};
17
18pub struct ScriptEngine {
20 lua: Lua,
21 loaded_scripts: HashMap<String, (String, RegistryKey)>,
22 command_queue: Arc<CommandQueue>,
23 elapsed_time: f32,
24 pub log_queue: Arc<std::sync::Mutex<Vec<(String, String)>>>, }
26
27unsafe impl Send for ScriptEngine {}
28unsafe impl Sync for ScriptEngine {}
29
30#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
32pub struct Script {
33 pub file_path: String,
34 #[serde(default, skip)]
35 pub initialized: bool, }
37
38impl Script {
39 pub fn new(path: &str) -> Self {
40 Self {
41 file_path: path.to_string(),
42 initialized: false,
43 }
44 }
45}
46
47#[derive(Clone, Debug)]
49pub struct ScriptContext {
50 pub entity_id: u32,
51 pub dt: f32,
52 pub position: [f32; 3],
53 pub velocity: [f32; 3],
54 pub key_w: bool,
55 pub key_a: bool,
56 pub key_s: bool,
57 pub key_d: bool,
58 pub key_space: bool,
59 pub key_up: bool,
60 pub key_down: bool,
61 pub key_left: bool,
62 pub key_right: bool,
63}
64
65#[derive(Clone, Debug, Default)]
67pub struct ScriptResult {
68 pub new_position: Option<[f32; 3]>,
69 pub new_velocity: Option<[f32; 3]>,
70}
71
72impl ScriptEngine {
73 pub fn new() -> Result<Self, LuaError> {
74 let lua = Lua::new();
75 let command_queue = Arc::new(CommandQueue::new());
76 let log_queue = Arc::new(std::sync::Mutex::new(Vec::new()));
77
78 lua.globals().set("os", LuaNil)?;
80 lua.globals().set("io", LuaNil)?;
81 lua.globals().set("loadfile", LuaNil)?;
82 lua.globals().set("dofile", LuaNil)?;
83 lua.globals().set("require", LuaNil)?;
84 lua.globals().set("package", LuaNil)?;
85 lua.globals().set("debug", LuaNil)?;
86 lua.globals().set("loadstring", LuaNil)?;
87 lua.globals().set("load", LuaNil)?;
88
89 let lq_clone1 = log_queue.clone();
91 lua.globals().set(
92 "print_engine",
93 lua.create_function(move |_, msg: String| {
94 if let Ok(mut q) = lq_clone1.lock() {
95 q.push(("info".to_string(), msg));
96 }
97 Ok(())
98 })?,
99 )?;
100
101 let lq_clone2 = log_queue.clone();
103 lua.globals().set(
104 "print",
105 lua.create_function(move |_, values: LuaMultiValue| {
106 let parts: Vec<String> = values
107 .iter()
108 .map(|v| {
109 if let mlua::Value::String(s) = v {
110 s.to_str().unwrap_or("").to_string()
111 } else if let mlua::Value::Number(n) = v {
112 n.to_string()
113 } else if let mlua::Value::Integer(i) = v {
114 i.to_string()
115 } else if let mlua::Value::Boolean(b) = v {
116 b.to_string()
117 } else {
118 format!("{:?}", v)
119 }
120 })
121 .collect();
122 if let Ok(mut q) = lq_clone2.lock() {
123 q.push(("info".to_string(), parts.join("\t")));
124 }
125 Ok(())
126 })?,
127 )?;
128
129 lua.load(
131 r#"
132 function vec3(x, y, z)
133 return { x = x or 0, y = y or 0, z = z or 0 }
134 end
135
136 function vec3_add(a, b)
137 return vec3(a.x + b.x, a.y + b.y, a.z + b.z)
138 end
139
140 function vec3_sub(a, b)
141 return vec3(a.x - b.x, a.y - b.y, a.z - b.z)
142 end
143
144 function vec3_scale(v, s)
145 return vec3(v.x * s, v.y * s, v.z * s)
146 end
147
148 function vec3_length(v)
149 return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
150 end
151
152 function vec3_normalize(v)
153 local len = vec3_length(v)
154 if len > 0.0001 then
155 return vec3(v.x / len, v.y / len, v.z / len)
156 end
157 return vec3(0, 0, 0)
158 end
159
160 function vec3_dot(a, b)
161 return a.x * b.x + a.y * b.y + a.z * b.z
162 end
163
164 function vec3_cross(a, b)
165 return vec3(
166 a.y * b.z - a.z * b.y,
167 a.z * b.x - a.x * b.z,
168 a.x * b.y - a.y * b.x
169 )
170 end
171
172 function vec3_lerp(a, b, t)
173 return vec3(
174 a.x + (b.x - a.x) * t,
175 a.y + (b.y - a.y) * t,
176 a.z + (b.z - a.z) * t
177 )
178 end
179
180 function vec3_distance(a, b)
181 return vec3_length(vec3_sub(a, b))
182 end
183
184 -- Clamp utility
185 function clamp(value, min, max)
186 return math.max(min, math.min(max, value))
187 end
188
189 -- Lerp utility
190 function lerp(a, b, t)
191 return a + (b - a) * t
192 end
193 "#,
194 )
195 .exec()?;
196
197 api_entity::register_entity_api(&lua, command_queue.clone())?;
199 api_input::register_input_api(&lua)?;
200 api_physics::register_physics_api(&lua, command_queue.clone())?;
201 api_scene::register_scene_api(&lua, command_queue.clone())?;
202 api_audio::register_audio_api(&lua, command_queue.clone())?;
203 api_time::register_time_api(&lua)?;
204 api_vehicle::register_vehicle_api(&lua, command_queue.clone())?;
205 api_ai::register_ai_api(&lua, command_queue.clone())?;
206
207 Ok(Self {
208 lua,
209 loaded_scripts: HashMap::new(),
210 command_queue,
211 elapsed_time: 0.0,
212 log_queue,
213 })
214 }
215
216 pub fn load_script(&mut self, path: &str) -> Result<(), String> {
217 let content = std::fs::read_to_string(path)
218 .map_err(|e| format!("Script okunamadı {}: {}", path, e))?;
219
220 let env = self.lua.create_table().map_err(|e| e.to_string())?;
221
222 let meta = self.lua.create_table().map_err(|e| e.to_string())?;
224 meta.set("__index", self.lua.globals()).unwrap();
225 env.set_metatable(Some(meta));
226
227 self.lua
229 .load(&content)
230 .set_environment(env.clone())
231 .exec()
232 .map_err(|e| format!("Lua hata {}: {}", path, e))?;
233
234 let key = self
235 .lua
236 .create_registry_value(env)
237 .map_err(|e| e.to_string())?;
238
239 if let Some((_, old_key)) = self.loaded_scripts.insert(path.to_string(), (content, key)) {
241 let _ = self.lua.remove_registry_value(old_key);
242 }
243
244 tracing::info!("🔧 ScriptEngine: Yüklendi ve İzole Edildi → {}", path);
245 Ok(())
246 }
247
248 pub fn update(&mut self, world: &World, input: &Input, dt: f32) -> Result<(), String> {
250 self.elapsed_time += dt;
251
252 api_entity::update_entity_read_api(&self.lua, world)
254 .map_err(|e| format!("Entity API güncelleme hatası: {}", e))?;
255 api_input::update_input_api(&self.lua, input)
256 .map_err(|e| format!("Input API güncelleme hatası: {}", e))?;
257 api_scene::update_scene_api(&self.lua, world)
258 .map_err(|e| format!("Scene API güncelleme hatası: {}", e))?;
259 api_time::update_time_api(&self.lua, dt, self.elapsed_time, 1.0 / dt.max(0.0001))
260 .map_err(|e| format!("Time API güncelleme hatası: {}", e))?;
261 api_physics::update_physics_api(&self.lua, world)
262 .map_err(|e| format!("Physics API güncelleme hatası: {}", e))?;
263
264 let globals = self.lua.globals();
266 if let Ok(func) = globals.get::<_, LuaFunction>("on_update") {
267 let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
268 ctx_table.set("dt", dt).map_err(|e| e.to_string())?;
269 ctx_table
270 .set("elapsed", self.elapsed_time)
271 .map_err(|e| e.to_string())?;
272
273 func.call::<_, ()>(ctx_table)
274 .map_err(|e| format!("Lua on_update hatası: {}", e))?;
275 }
276
277 Ok(())
278 }
279
280 pub fn update_entity(
282 &mut self,
283 entity_id: u32,
284 script_path: &str,
285 dt: f32,
286 ) -> Result<(), String> {
287 if let Some((_, key)) = self.loaded_scripts.get(script_path) {
288 let env: mlua::Table = self.lua.registry_value(key).map_err(|e| e.to_string())?;
289
290 if let Ok(func) = env.get::<_, LuaFunction>("on_entity_update") {
292 func.call::<_, ()>((entity_id, dt)).map_err(|e| {
293 format!(
294 "Lua on_entity_update hatası (entity {} mod {}): {}",
295 entity_id, script_path, e
296 )
297 })?;
298 }
299 }
300 Ok(())
301 }
302
303 pub fn flush_commands(&self, world: &mut World, dt: f32) -> Vec<ScriptCommand> {
305 let commands = self.command_queue.drain();
306 let mut unhandled = Vec::new();
307
308 for cmd in commands {
309 match cmd {
310 ScriptCommand::SetPosition(id, pos) => {
311 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
312 if let Some(t) = transforms.get_mut(id) {
313 t.position = pos;
314 }
315 }
316 ScriptCommand::SetRotation(id, rot) => {
317 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
318 if let Some(t) = transforms.get_mut(id) {
319 t.rotation = rot;
320 }
321 }
322 ScriptCommand::SetScale(id, scale) => {
323 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
324 if let Some(t) = transforms.get_mut(id) {
325 t.scale = scale;
326 }
327 }
328 ScriptCommand::SetVelocity(id, vel) => {
329 let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
330 if let Some(v) = velocities.get_mut(id) {
331 v.linear = vel;
332 }
333 }
334 ScriptCommand::SetAngularVelocity(id, ang_vel) => {
335 let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
336 if let Some(v) = velocities.get_mut(id) {
337 v.angular = ang_vel;
338 }
339 }
340 ScriptCommand::ApplyForce(id, force) => {
341 let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
342 if let Some(rb) = rbs.get(id) {
343 if rb.mass > 0.0 {
344 let accel = force * (1.0 / rb.mass);
345 drop(rbs);
346 let mut vels =
347 world.borrow_mut::<gizmo_physics::components::Velocity>();
348 if let Some(v) = vels.get_mut(id) {
349 v.linear += accel * dt;
350 }
351 }
352 }
353 }
354 ScriptCommand::ApplyImpulse(id, impulse) => {
355 let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
356 if let Some(rb) = rbs.get(id) {
357 if rb.mass > 0.0 {
358 let delta_v = impulse * (1.0 / rb.mass);
359 drop(rbs);
360 let mut vels =
361 world.borrow_mut::<gizmo_physics::components::Velocity>();
362 if let Some(v) = vels.get_mut(id) {
363 v.linear += delta_v;
364 }
365 }
366 }
367 }
368 ScriptCommand::AddRigidBody {
369 id,
370 mass,
371 restitution,
372 friction,
373 use_gravity,
374 } => {
375 let entity = world
376 .iter_alive_entities()
377 .into_iter()
378 .find(|e| e.id() == id);
379 if let Some(e) = entity {
380 let rb = gizmo_physics::components::RigidBody::new(
381 mass,
382 restitution,
383 friction,
384 use_gravity,
385 );
386 world.add_component(e, rb);
387 if world
389 .borrow::<gizmo_physics::components::Velocity>()
390 .get(id)
391 .is_none()
392 {
393 world.add_component(
394 e,
395 gizmo_physics::components::Velocity::new(gizmo_math::Vec3::ZERO),
396 );
397 }
398 }
399 }
400 ScriptCommand::AddBoxCollider { id, hx, hy, hz } => {
401 let entity = world
402 .iter_alive_entities()
403 .into_iter()
404 .find(|e| e.id() == id);
405 if let Some(e) = entity {
406 let col =
407 gizmo_physics::shape::Collider::aabb(gizmo_math::Vec3::new(hx, hy, hz));
408 world.add_component(e, col);
409 }
410 }
411 ScriptCommand::AddSphereCollider { id, radius } => {
412 let entity = world
413 .iter_alive_entities()
414 .into_iter()
415 .find(|e| e.id() == id);
416 if let Some(e) = entity {
417 let col = gizmo_physics::shape::Collider::sphere(radius);
418 world.add_component(e, col);
419 }
420 }
421
422 ScriptCommand::SetVehicleEngineForce(_id, _force) => {}
423 ScriptCommand::SetVehicleSteering(_id, _angle) => {}
424 ScriptCommand::SetVehicleBrake(_id, _force) => {}
425
426 ScriptCommand::SpawnEntity { name, position } => {
427 let entity = world.spawn();
428 world.add_component(entity, gizmo_core::EntityName::new(&name));
429 world
430 .add_component(entity, gizmo_physics::components::Transform::new(position));
431 let msg = format!(
432 "Entity spawn: '{}' at ({:.1}, {:.1}, {:.1})",
433 name, position.x, position.y, position.z
434 );
435 if let Ok(mut q) = self.log_queue.lock() {
436 q.push(("info".to_string(), msg));
437 }
438 }
439 ScriptCommand::SpawnPrefab {
440 name,
441 prefab_type,
442 position,
443 } => {
444 let entity = world.spawn();
445 world.add_component(entity, gizmo_core::EntityName::new(&name));
446 world
447 .add_component(entity, gizmo_physics::components::Transform::new(position));
448 world.add_component(entity, gizmo_core::PrefabRequest(prefab_type.clone()));
449 }
450 ScriptCommand::DestroyEntity(id) => {
451 world.despawn_by_id(id);
452 if let Ok(mut q) = self.log_queue.lock() {
453 q.push(("info".to_string(), format!("Entity destroyed: {}", id)));
454 }
455 }
456 ScriptCommand::SetEntityName(id, name) => {
457 let mut names = world.borrow_mut::<gizmo_core::EntityName>();
458 if let Some(n) = names.get_mut(id) {
459 n.0 = name;
460 }
461 }
462 ScriptCommand::AddNavAgent(id) => {
463 let entity = world
464 .iter_alive_entities()
465 .into_iter()
466 .find(|e| e.id() == id);
467 if let Some(e) = entity {
468 world.add_component(e, gizmo_ai::components::NavAgent::default());
469 }
470 }
471 ScriptCommand::SetAiTarget(id, target) => {
472 let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
473 if let Some(agent) = agents.get_mut(id) {
474 agent.set_target(target);
475 }
476 }
477 ScriptCommand::ClearAiTarget(id) => {
478 let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
479 if let Some(agent) = agents.get_mut(id) {
480 agent.clear_path();
481 }
482 }
483 ScriptCommand::SaveScene(_)
484 | ScriptCommand::ShowDialogue { .. }
485 | ScriptCommand::HideDialogue
486 | ScriptCommand::TriggerCutscene(_)
487 | ScriptCommand::EndCutscene
488 | ScriptCommand::AddCheckpoint { .. }
489 | ScriptCommand::ActivateCheckpoint(_)
490 | ScriptCommand::StartRace
491 | ScriptCommand::FinishRace { .. }
492 | ScriptCommand::ResetRace
493 | ScriptCommand::SetCameraTarget(_)
494 | ScriptCommand::SetCameraFov(_) => {
495 }
497 other => {
498 unhandled.push(other);
499 }
500 }
501 }
502
503 unhandled
504 }
505
506 pub fn get_pending_audio_scene_commands(&self) -> Vec<ScriptCommand> {
508 Vec::new()
511 }
512
513 pub fn reload_if_changed(&mut self, path: &str) -> Result<bool, String> {
515 let current =
516 std::fs::read_to_string(path).map_err(|e| format!("Script okunamadı: {}", e))?;
517
518 if let Some((cached_code, _)) = self.loaded_scripts.get(path) {
519 if *cached_code == current {
520 return Ok(false);
521 }
522 }
523
524 self.load_script(path)?;
525 Ok(true)
526 }
527
528 pub fn has_function(&self, path: &str, name: &str) -> bool {
530 if let Some((_, key)) = self.loaded_scripts.get(path) {
531 if let Ok(env) = self.lua.registry_value::<mlua::Table>(key) {
532 return env.get::<_, LuaFunction>(name).is_ok();
533 }
534 }
535 false
536 }
537
538 pub fn run_entity_update(
540 &self,
541 path: &str,
542 func_name: &str,
543 ctx: &ScriptContext,
544 ) -> Result<ScriptResult, String> {
545 let env: mlua::Table = if let Some((_, key)) = self.loaded_scripts.get(path) {
546 self.lua.registry_value(key).map_err(|e| e.to_string())?
547 } else {
548 return Err(format!("Script not loaded: {}", path));
549 };
550
551 let func: LuaFunction = match env.get(func_name) {
552 Ok(f) => f,
553 Err(_) => return Ok(ScriptResult::default()),
554 };
555
556 let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
557 ctx_table
558 .set("entity_id", ctx.entity_id)
559 .map_err(|e| e.to_string())?;
560 ctx_table.set("dt", ctx.dt).map_err(|e| e.to_string())?;
561 ctx_table
562 .set("elapsed", self.elapsed_time)
563 .map_err(|e| e.to_string())?;
564
565 let pos = self.lua.create_table().map_err(|e| e.to_string())?;
566 pos.set("x", ctx.position[0]).map_err(|e| e.to_string())?;
567 pos.set("y", ctx.position[1]).map_err(|e| e.to_string())?;
568 pos.set("z", ctx.position[2]).map_err(|e| e.to_string())?;
569 ctx_table.set("position", pos).map_err(|e| e.to_string())?;
570
571 let vel = self.lua.create_table().map_err(|e| e.to_string())?;
572 vel.set("x", ctx.velocity[0]).map_err(|e| e.to_string())?;
573 vel.set("y", ctx.velocity[1]).map_err(|e| e.to_string())?;
574 vel.set("z", ctx.velocity[2]).map_err(|e| e.to_string())?;
575 ctx_table.set("velocity", vel).map_err(|e| e.to_string())?;
576
577 let input = self.lua.create_table().map_err(|e| e.to_string())?;
578 input.set("w", ctx.key_w).map_err(|e| e.to_string())?;
579 input.set("a", ctx.key_a).map_err(|e| e.to_string())?;
580 input.set("s", ctx.key_s).map_err(|e| e.to_string())?;
581 input.set("d", ctx.key_d).map_err(|e| e.to_string())?;
582 input
583 .set("space", ctx.key_space)
584 .map_err(|e| e.to_string())?;
585 input.set("up", ctx.key_up).map_err(|e| e.to_string())?;
586 input.set("down", ctx.key_down).map_err(|e| e.to_string())?;
587 input.set("left", ctx.key_left).map_err(|e| e.to_string())?;
588 input
589 .set("right", ctx.key_right)
590 .map_err(|e| e.to_string())?;
591 ctx_table.set("input", input).map_err(|e| e.to_string())?;
592
593 let result_table: LuaTable = func
594 .call(ctx_table)
595 .map_err(|e| format!("Lua runtime: {}", e))?;
596
597 let mut result = ScriptResult::default();
598
599 if let Ok(pos) = result_table.get::<_, LuaTable>("position") {
600 let x: f32 = pos.get("x").unwrap_or(0.0);
601 let y: f32 = pos.get("y").unwrap_or(0.0);
602 let z: f32 = pos.get("z").unwrap_or(0.0);
603 result.new_position = Some([x, y, z]);
604 }
605
606 if let Ok(vel) = result_table.get::<_, LuaTable>("velocity") {
607 let x: f32 = vel.get("x").unwrap_or(0.0);
608 let y: f32 = vel.get("y").unwrap_or(0.0);
609 let z: f32 = vel.get("z").unwrap_or(0.0);
610 result.new_velocity = Some([x, y, z]);
611 }
612
613 Ok(result)
614 }
615
616 pub fn command_queue(&self) -> &Arc<CommandQueue> {
618 &self.command_queue
619 }
620}
621
622gizmo_core::impl_component!(Script);