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