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 println!("🔧 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
262 let globals = self.lua.globals();
264 if let Ok(func) = globals.get::<_, LuaFunction>("on_update") {
265 let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
266 ctx_table.set("dt", dt).map_err(|e| e.to_string())?;
267 ctx_table
268 .set("elapsed", self.elapsed_time)
269 .map_err(|e| e.to_string())?;
270
271 func.call::<_, ()>(ctx_table)
272 .map_err(|e| format!("Lua on_update hatası: {}", e))?;
273 }
274
275 Ok(())
276 }
277
278 pub fn update_entity(
280 &mut self,
281 entity_id: u32,
282 script_path: &str,
283 dt: f32,
284 ) -> Result<(), String> {
285 if let Some((_, key)) = self.loaded_scripts.get(script_path) {
286 let env: mlua::Table = self.lua.registry_value(key).map_err(|e| e.to_string())?;
287
288 if let Ok(func) = env.get::<_, LuaFunction>("on_entity_update") {
290 func.call::<_, ()>((entity_id, dt)).map_err(|e| {
291 format!(
292 "Lua on_entity_update hatası (entity {} mod {}): {}",
293 entity_id, script_path, e
294 )
295 })?;
296 }
297 }
298 Ok(())
299 }
300
301 pub fn flush_commands(&self, world: &mut World, dt: f32) -> Vec<ScriptCommand> {
303 let commands = self.command_queue.drain();
304 let mut unhandled = Vec::new();
305
306 for cmd in commands {
307 match cmd {
308 ScriptCommand::SetPosition(id, pos) => {
309 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
310 if let Some(t) = transforms.get_mut(id) {
311 t.position = pos;
312 }
313 }
314 ScriptCommand::SetRotation(id, rot) => {
315 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
316 if let Some(t) = transforms.get_mut(id) {
317 t.rotation = rot;
318 }
319 }
320 ScriptCommand::SetScale(id, scale) => {
321 let mut transforms = world.borrow_mut::<gizmo_physics::components::Transform>();
322 if let Some(t) = transforms.get_mut(id) {
323 t.scale = scale;
324 }
325 }
326 ScriptCommand::SetVelocity(id, vel) => {
327 let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
328 if let Some(v) = velocities.get_mut(id) {
329 v.linear = vel;
330 }
331 }
332 ScriptCommand::SetAngularVelocity(id, ang_vel) => {
333 let mut velocities = world.borrow_mut::<gizmo_physics::components::Velocity>();
334 if let Some(v) = velocities.get_mut(id) {
335 v.angular = ang_vel;
336 }
337 }
338 ScriptCommand::ApplyForce(id, force) => {
339 let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
340 if let Some(rb) = rbs.get(id) {
341 if rb.mass > 0.0 {
342 let accel = force * (1.0 / rb.mass);
343 drop(rbs);
344 let mut vels =
345 world.borrow_mut::<gizmo_physics::components::Velocity>();
346 if let Some(v) = vels.get_mut(id) {
347 v.linear += accel * dt;
348 }
349 }
350 }
351 }
352 ScriptCommand::ApplyImpulse(id, impulse) => {
353 let rbs = world.borrow::<gizmo_physics::components::RigidBody>();
354 if let Some(rb) = rbs.get(id) {
355 if rb.mass > 0.0 {
356 let delta_v = impulse * (1.0 / rb.mass);
357 drop(rbs);
358 let mut vels =
359 world.borrow_mut::<gizmo_physics::components::Velocity>();
360 if let Some(v) = vels.get_mut(id) {
361 v.linear += delta_v;
362 }
363 }
364 }
365 }
366 ScriptCommand::AddRigidBody {
367 id,
368 mass,
369 restitution,
370 friction,
371 use_gravity,
372 } => {
373 let entity = world
374 .iter_alive_entities()
375 .into_iter()
376 .find(|e| e.id() == id);
377 if let Some(e) = entity {
378 let rb = gizmo_physics::components::RigidBody::new(
379 mass,
380 restitution,
381 friction,
382 use_gravity,
383 );
384 world.add_component(e, rb);
385 if world
387 .borrow::<gizmo_physics::components::Velocity>()
388 .get(id)
389 .is_none()
390 {
391 world.add_component(
392 e,
393 gizmo_physics::components::Velocity::new(gizmo_math::Vec3::ZERO),
394 );
395 }
396 }
397 }
398 ScriptCommand::AddBoxCollider { id, hx, hy, hz } => {
399 let entity = world
400 .iter_alive_entities()
401 .into_iter()
402 .find(|e| e.id() == id);
403 if let Some(e) = entity {
404 let col =
405 gizmo_physics::shape::Collider::aabb(gizmo_math::Vec3::new(hx, hy, hz));
406 world.add_component(e, col);
407 }
408 }
409 ScriptCommand::AddSphereCollider { id, radius } => {
410 let entity = world
411 .iter_alive_entities()
412 .into_iter()
413 .find(|e| e.id() == id);
414 if let Some(e) = entity {
415 let col = gizmo_physics::shape::Collider::sphere(radius);
416 world.add_component(e, col);
417 }
418 }
419
420 ScriptCommand::SetVehicleEngineForce(_id, _force) => {}
421 ScriptCommand::SetVehicleSteering(_id, _angle) => {}
422 ScriptCommand::SetVehicleBrake(_id, _force) => {}
423
424 ScriptCommand::SpawnEntity { name, position } => {
425 let entity = world.spawn();
426 world.add_component(entity, gizmo_core::EntityName::new(&name));
427 world
428 .add_component(entity, gizmo_physics::components::Transform::new(position));
429 let msg = format!(
430 "Entity spawn: '{}' at ({:.1}, {:.1}, {:.1})",
431 name, position.x, position.y, position.z
432 );
433 if let Ok(mut q) = self.log_queue.lock() {
434 q.push(("info".to_string(), msg));
435 }
436 }
437 ScriptCommand::SpawnPrefab {
438 name,
439 prefab_type,
440 position,
441 } => {
442 let entity = world.spawn();
443 world.add_component(entity, gizmo_core::EntityName::new(&name));
444 world
445 .add_component(entity, gizmo_physics::components::Transform::new(position));
446 world.add_component(entity, gizmo_core::PrefabRequest(prefab_type.clone()));
447 }
448 ScriptCommand::DestroyEntity(id) => {
449 world.despawn_by_id(id);
450 if let Ok(mut q) = self.log_queue.lock() {
451 q.push(("info".to_string(), format!("Entity destroyed: {}", id)));
452 }
453 }
454 ScriptCommand::SetEntityName(id, name) => {
455 let mut names = world.borrow_mut::<gizmo_core::EntityName>();
456 if let Some(n) = names.get_mut(id) {
457 n.0 = name;
458 }
459 }
460 ScriptCommand::AddNavAgent(id) => {
461 let entity = world
462 .iter_alive_entities()
463 .into_iter()
464 .find(|e| e.id() == id);
465 if let Some(e) = entity {
466 world.add_component(e, gizmo_ai::components::NavAgent::default());
467 }
468 }
469 ScriptCommand::SetAiTarget(id, target) => {
470 let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
471 if let Some(agent) = agents.get_mut(id) {
472 agent.set_target(target);
473 }
474 }
475 ScriptCommand::ClearAiTarget(id) => {
476 let mut agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
477 if let Some(agent) = agents.get_mut(id) {
478 agent.clear_path();
479 }
480 }
481 ScriptCommand::SaveScene(_)
482 | ScriptCommand::ShowDialogue { .. }
483 | ScriptCommand::HideDialogue
484 | ScriptCommand::TriggerCutscene(_)
485 | ScriptCommand::EndCutscene
486 | ScriptCommand::AddCheckpoint { .. }
487 | ScriptCommand::ActivateCheckpoint(_)
488 | ScriptCommand::StartRace
489 | ScriptCommand::FinishRace { .. }
490 | ScriptCommand::ResetRace
491 | ScriptCommand::SetCameraTarget(_)
492 | ScriptCommand::SetCameraFov(_) => {
493 }
495 other => {
496 unhandled.push(other);
497 }
498 }
499 }
500
501 unhandled
502 }
503
504 pub fn get_pending_audio_scene_commands(&self) -> Vec<ScriptCommand> {
506 Vec::new()
509 }
510
511 pub fn reload_if_changed(&mut self, path: &str) -> Result<bool, String> {
513 let current =
514 std::fs::read_to_string(path).map_err(|e| format!("Script okunamadı: {}", e))?;
515
516 if let Some((cached_code, _)) = self.loaded_scripts.get(path) {
517 if *cached_code == current {
518 return Ok(false);
519 }
520 }
521
522 self.load_script(path)?;
523 Ok(true)
524 }
525
526 pub fn has_function(&self, path: &str, name: &str) -> bool {
528 if let Some((_, key)) = self.loaded_scripts.get(path) {
529 if let Ok(env) = self.lua.registry_value::<mlua::Table>(key) {
530 return env.get::<_, LuaFunction>(name).is_ok();
531 }
532 }
533 false
534 }
535
536 pub fn run_entity_update(
538 &self,
539 path: &str,
540 func_name: &str,
541 ctx: &ScriptContext,
542 ) -> Result<ScriptResult, String> {
543 let env: mlua::Table = if let Some((_, key)) = self.loaded_scripts.get(path) {
544 self.lua.registry_value(key).map_err(|e| e.to_string())?
545 } else {
546 return Err(format!("Script not loaded: {}", path));
547 };
548
549 let func: LuaFunction = match env.get(func_name) {
550 Ok(f) => f,
551 Err(_) => return Ok(ScriptResult::default()),
552 };
553
554 let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
555 ctx_table
556 .set("entity_id", ctx.entity_id)
557 .map_err(|e| e.to_string())?;
558 ctx_table.set("dt", ctx.dt).map_err(|e| e.to_string())?;
559 ctx_table
560 .set("elapsed", self.elapsed_time)
561 .map_err(|e| e.to_string())?;
562
563 let pos = self.lua.create_table().map_err(|e| e.to_string())?;
564 pos.set("x", ctx.position[0]).map_err(|e| e.to_string())?;
565 pos.set("y", ctx.position[1]).map_err(|e| e.to_string())?;
566 pos.set("z", ctx.position[2]).map_err(|e| e.to_string())?;
567 ctx_table.set("position", pos).map_err(|e| e.to_string())?;
568
569 let vel = self.lua.create_table().map_err(|e| e.to_string())?;
570 vel.set("x", ctx.velocity[0]).map_err(|e| e.to_string())?;
571 vel.set("y", ctx.velocity[1]).map_err(|e| e.to_string())?;
572 vel.set("z", ctx.velocity[2]).map_err(|e| e.to_string())?;
573 ctx_table.set("velocity", vel).map_err(|e| e.to_string())?;
574
575 let input = self.lua.create_table().map_err(|e| e.to_string())?;
576 input.set("w", ctx.key_w).map_err(|e| e.to_string())?;
577 input.set("a", ctx.key_a).map_err(|e| e.to_string())?;
578 input.set("s", ctx.key_s).map_err(|e| e.to_string())?;
579 input.set("d", ctx.key_d).map_err(|e| e.to_string())?;
580 input
581 .set("space", ctx.key_space)
582 .map_err(|e| e.to_string())?;
583 input.set("up", ctx.key_up).map_err(|e| e.to_string())?;
584 input.set("down", ctx.key_down).map_err(|e| e.to_string())?;
585 input.set("left", ctx.key_left).map_err(|e| e.to_string())?;
586 input
587 .set("right", ctx.key_right)
588 .map_err(|e| e.to_string())?;
589 ctx_table.set("input", input).map_err(|e| e.to_string())?;
590
591 let result_table: LuaTable = func
592 .call(ctx_table)
593 .map_err(|e| format!("Lua runtime: {}", e))?;
594
595 let mut result = ScriptResult::default();
596
597 if let Ok(pos) = result_table.get::<_, LuaTable>("position") {
598 let x: f32 = pos.get("x").unwrap_or(0.0);
599 let y: f32 = pos.get("y").unwrap_or(0.0);
600 let z: f32 = pos.get("z").unwrap_or(0.0);
601 result.new_position = Some([x, y, z]);
602 }
603
604 if let Ok(vel) = result_table.get::<_, LuaTable>("velocity") {
605 let x: f32 = vel.get("x").unwrap_or(0.0);
606 let y: f32 = vel.get("y").unwrap_or(0.0);
607 let z: f32 = vel.get("z").unwrap_or(0.0);
608 result.new_velocity = Some([x, y, z]);
609 }
610
611 Ok(result)
612 }
613
614 pub fn command_queue(&self) -> &Arc<CommandQueue> {
616 &self.command_queue
617 }
618}
619
620gizmo_core::impl_component!(Script);