Skip to main content

gizmo_scripting/
engine.rs

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
19/// Lua Scripting Motoru — Genişletilmiş API ile oyun mantığını yönetir
20pub 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)>>>, // (Level, Message)
26}
27
28unsafe impl Send for ScriptEngine {}
29unsafe impl Sync for ScriptEngine {}
30
31/// ECS Componenti: Varlığın üzerine hangi Lua script'inin takılı olduğunu tutar
32#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
33pub struct Script {
34    pub file_path: String,
35    #[serde(default, skip)]
36    pub initialized: bool, // on_init çağrıldı mı?
37}
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/// Lua'ya geçirilecek entity verisi (geriye dönük uyumluluk için)
49#[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/// Lua'dan dönen değişiklikler (geriye dönük uyumluluk)
67#[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        // === SANDBOX: Tehlikeli modülleri kapat ===
80        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        // === TEMEL PRINT FONKSİYONU ===
91        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        // Orijinal print'i de engine çıktısına yönlendir
103        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        // === VEC3 YARDIMCI FONKSİYONLARI ===
131        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 MODÜLLERİNİ KAYDET ===
199        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        // Link to _G via metatable
225        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        // Script'i İzole env içinde çalıştır
230        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        // Replace existing key if it exists to free old memory
242        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    /// Her frame çağrılan güncelleme — World verilerini Lua'ya aktarır, scriptleri çalıştırır
251    pub fn update(&mut self, world: &World, input: &Input, dt: f32) -> Result<(), String> {
252        self.elapsed_time += dt;
253
254        // 1. World verilerini Lua'ya aktar (read snapshot)
255        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        // 2. on_update callback'ini çağır (varsa)
269        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    /// Per-entity script güncelleme — Script component'i olan entity'ler için izole ortamda çalıştırır
285    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            // on_entity_update(entity_id, dt) çağır (varsa)
295            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    /// Komut kuyruğundaki tüm komutları World'e uygular ve oyun mantığı için kalan komutları döndürür
308    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 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 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 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 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 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 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 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.reconstruct_entity(id);
380                    if let Some(e) = entity {
381                        let rb = gizmo_physics_rigid::components::RigidBody::new(
382                            mass,
383                            restitution,
384                            friction,
385                            use_gravity,
386                        );
387                        world.add_component(e, rb);
388                        // Make sure velocity exists so it can move
389                        if world
390                            .borrow::<gizmo_physics_rigid::components::Velocity>()
391                            .get(id)
392                            .is_none()
393                        {
394                            world.add_component(
395                                e,
396                                gizmo_physics_rigid::components::Velocity::new(gizmo_math::Vec3::ZERO),
397                            );
398                        }
399                    }
400                }
401                ScriptCommand::AddBoxCollider { id, hx, hy, hz } => {
402                    let entity = world.reconstruct_entity(id);
403                    if let Some(e) = entity {
404                        let col =
405                            gizmo_physics_core::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.reconstruct_entity(id);
411                    if let Some(e) = entity {
412                        let col = gizmo_physics_core::Collider::sphere(radius);
413                        world.add_component(e, col);
414                    }
415                }
416
417                ScriptCommand::SetVehicleEngineForce(_id, _force) => {}
418                ScriptCommand::SetVehicleSteering(_id, _angle) => {}
419                ScriptCommand::SetVehicleBrake(_id, _force) => {}
420
421                ScriptCommand::SpawnEntity { name, position } => {
422                    let entity = world.spawn();
423                    world.add_component(entity, gizmo_core::EntityName::new(&name));
424                    world
425                        .add_component(entity, gizmo_physics_core::Transform::new(position));
426                    let msg = format!(
427                        "Entity spawn: '{}' at ({:.1}, {:.1}, {:.1})",
428                        name, position.x, position.y, position.z
429                    );
430                    if let Ok(mut q) = self.log_queue.lock() {
431                        q.push(("info".to_string(), msg));
432                    }
433                }
434                ScriptCommand::SpawnPrefab {
435                    name,
436                    prefab_type,
437                    position,
438                } => {
439                    let entity = world.spawn();
440                    world.add_component(entity, gizmo_core::EntityName::new(&name));
441                    world
442                        .add_component(entity, gizmo_physics_core::Transform::new(position));
443                    world.add_component(entity, gizmo_core::PrefabRequest(prefab_type.clone()));
444                }
445                ScriptCommand::DestroyEntity(id) => {
446                    world.despawn_by_id(id);
447                    if let Ok(mut q) = self.log_queue.lock() {
448                        q.push(("info".to_string(), format!("Entity destroyed: {}", id)));
449                    }
450                }
451ScriptCommand::SetEntityName(id, name) => {
452                    let names = world.borrow_mut::<gizmo_core::EntityName>();
453                    if let Some(mut n) = names.get_mut(id) {
454                        n.0 = name;
455                    }
456                }
457ScriptCommand::PlayAnimation { id, name, blend, loop_anim } => {
458                    let players = world.borrow_mut::<gizmo_renderer::components::AnimationPlayer>();
459                    if let Some(mut player) = players.get_mut(id) {
460                        player.play_animation_by_name(&name, blend, loop_anim);
461                    }
462                }
463                ScriptCommand::SetAnimationSpeed(id, speed) => {
464                    let players = world.borrow_mut::<gizmo_renderer::components::AnimationPlayer>();
465                    if let Some(mut player) = players.get_mut(id) {
466                        player.speed = speed;
467                    }
468                }
469                ScriptCommand::AddNavAgent(id) => {
470                    let entity = world.reconstruct_entity(id);
471                    if let Some(e) = entity {
472                        world.add_component(e, gizmo_ai::components::NavAgent::default());
473                    }
474                }
475                ScriptCommand::SetAiTarget(id, target) => {
476                    let agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
477                    if let Some(mut agent) = agents.get_mut(id) {
478                        agent.set_target(target);
479                    }
480                }
481ScriptCommand::ClearAiTarget(id) => {
482                    let agents = world.borrow_mut::<gizmo_ai::components::NavAgent>();
483                    if let Some(mut agent) = agents.get_mut(id) {
484                        agent.clear_path();
485                    }
486                }
487                ScriptCommand::SetFighterMove { id, name, startup, active, recovery, damage } => {
488                    let fighters = world.borrow_mut::<gizmo_physics_core::components::FighterController>();
489                    if let Some(mut fighter) = fighters.get_mut(id) {
490                        fighter.active_move = Some(gizmo_physics_core::components::fighter::CombatMove {
491                            name,
492                            frame_data: gizmo_physics_core::components::fighter::FrameData {
493                                startup,
494                                active,
495                                recovery,
496                                damage,
497                                ..Default::default()
498                            }
499                        });
500                        fighter.current_move_frame = 0;
501                    }
502                }
503                ScriptCommand::ApplyHitstop(id, frames) => {
504                    let fighters = world.borrow_mut::<gizmo_physics_core::components::FighterController>();
505                    if let Some(mut fighter) = fighters.get_mut(id) {
506                        fighter.apply_hitstop(frames);
507                    }
508                }
509                ScriptCommand::ApplyHitstun(id, frames) => {
510                    let fighters = world.borrow_mut::<gizmo_physics_core::components::FighterController>();
511                    if let Some(mut fighter) = fighters.get_mut(id) {
512                        fighter.apply_hitstun(frames);
513                    }
514                }
515                ScriptCommand::SaveScene(_)
516                | ScriptCommand::ShowDialogue { .. }
517                | ScriptCommand::HideDialogue
518                | ScriptCommand::TriggerCutscene(_)
519                | ScriptCommand::EndCutscene
520                | ScriptCommand::AddCheckpoint { .. }
521                | ScriptCommand::ActivateCheckpoint(_)
522                | ScriptCommand::StartRace
523                | ScriptCommand::FinishRace { .. }
524                | ScriptCommand::ResetRace
525                | ScriptCommand::SetCameraTarget(_)
526                | ScriptCommand::SetCameraFov(_)
527                | ScriptCommand::SetFightCamera { .. } => {
528                    // Bu komutlar flush_commands'ın dönüş değerinde (unhandled) zaten yer alacak
529                }
530                other => {
531                    unhandled.push(other);
532                }
533            }
534        }
535
536        unhandled
537    }
538
539    /// Runtime'da bekleyen ses/sahne komutlarını döndürür (demo tarafında ele alınır)
540    pub fn get_pending_audio_scene_commands(&self) -> Vec<ScriptCommand> {
541        // Flush zaten çağrıldıysa bu boş dönecek
542        // Alternatif: flush'tan önce çağrılmalı
543        Vec::new()
544    }
545
546    /// Script'in hot-reload edilip edilmeyeceğini kontrol eder
547    pub fn reload_if_changed(&mut self, path: &str) -> Result<bool, String> {
548        let current =
549            std::fs::read_to_string(path).map_err(|e| format!("Script okunamadı: {}", e))?;
550
551        if let Some((cached_code, _)) = self.loaded_scripts.get(path) {
552            if *cached_code == current {
553                return Ok(false);
554            }
555        }
556
557        self.load_script(path)?;
558        Ok(true)
559    }
560
561    /// Belirli bir isimdeki Lua fonksiyonunun var olup olmadığını kontrol eder
562    pub fn has_function(&self, path: &str, name: &str) -> bool {
563        if let Some((_, key)) = self.loaded_scripts.get(path) {
564            if let Ok(env) = self.lua.registry_value::<mlua::Table>(key) {
565                return env.get::<_, LuaFunction>(name).is_ok();
566            }
567        }
568        false
569    }
570
571    /// Belirli bir isimdeki Lua fonksiyonunu çağırır (per-entity scriptler için)
572    pub fn run_entity_update(
573        &self,
574        path: &str,
575        func_name: &str,
576        ctx: &ScriptContext,
577    ) -> Result<ScriptResult, String> {
578        let env: mlua::Table = if let Some((_, key)) = self.loaded_scripts.get(path) {
579            self.lua.registry_value(key).map_err(|e| e.to_string())?
580        } else {
581            return Err(format!("Script not loaded: {}", path));
582        };
583
584        let func: LuaFunction = match env.get(func_name) {
585            Ok(f) => f,
586            Err(_) => return Ok(ScriptResult::default()),
587        };
588
589        let ctx_table = self.lua.create_table().map_err(|e| e.to_string())?;
590        ctx_table
591            .set("entity_id", ctx.entity_id)
592            .map_err(|e| e.to_string())?;
593        ctx_table.set("dt", ctx.dt).map_err(|e| e.to_string())?;
594        ctx_table
595            .set("elapsed", self.elapsed_time)
596            .map_err(|e| e.to_string())?;
597
598        let pos = self.lua.create_table().map_err(|e| e.to_string())?;
599        pos.set("x", ctx.position[0]).map_err(|e| e.to_string())?;
600        pos.set("y", ctx.position[1]).map_err(|e| e.to_string())?;
601        pos.set("z", ctx.position[2]).map_err(|e| e.to_string())?;
602        ctx_table.set("position", pos).map_err(|e| e.to_string())?;
603
604        let vel = self.lua.create_table().map_err(|e| e.to_string())?;
605        vel.set("x", ctx.velocity[0]).map_err(|e| e.to_string())?;
606        vel.set("y", ctx.velocity[1]).map_err(|e| e.to_string())?;
607        vel.set("z", ctx.velocity[2]).map_err(|e| e.to_string())?;
608        ctx_table.set("velocity", vel).map_err(|e| e.to_string())?;
609
610        let input = self.lua.create_table().map_err(|e| e.to_string())?;
611        input.set("w", ctx.key_w).map_err(|e| e.to_string())?;
612        input.set("a", ctx.key_a).map_err(|e| e.to_string())?;
613        input.set("s", ctx.key_s).map_err(|e| e.to_string())?;
614        input.set("d", ctx.key_d).map_err(|e| e.to_string())?;
615        input
616            .set("space", ctx.key_space)
617            .map_err(|e| e.to_string())?;
618        input.set("up", ctx.key_up).map_err(|e| e.to_string())?;
619        input.set("down", ctx.key_down).map_err(|e| e.to_string())?;
620        input.set("left", ctx.key_left).map_err(|e| e.to_string())?;
621        input
622            .set("right", ctx.key_right)
623            .map_err(|e| e.to_string())?;
624        ctx_table.set("input", input).map_err(|e| e.to_string())?;
625
626        let result_table: LuaTable = func
627            .call(ctx_table)
628            .map_err(|e| format!("Lua runtime: {}", e))?;
629
630        let mut result = ScriptResult::default();
631
632        if let Ok(pos) = result_table.get::<_, LuaTable>("position") {
633            let x: f32 = pos.get("x").unwrap_or(0.0);
634            let y: f32 = pos.get("y").unwrap_or(0.0);
635            let z: f32 = pos.get("z").unwrap_or(0.0);
636            result.new_position = Some([x, y, z]);
637        }
638
639        if let Ok(vel) = result_table.get::<_, LuaTable>("velocity") {
640            let x: f32 = vel.get("x").unwrap_or(0.0);
641            let y: f32 = vel.get("y").unwrap_or(0.0);
642            let z: f32 = vel.get("z").unwrap_or(0.0);
643            result.new_velocity = Some([x, y, z]);
644        }
645
646        Ok(result)
647    }
648
649    /// Komut kuyruğuna doğrudan erişim (internals)
650    pub fn command_queue(&self) -> &Arc<CommandQueue> {
651        &self.command_queue
652    }
653}
654
655gizmo_core::impl_component!(Script);