sylt_std/
lingon.rs

1use crate as sylt_std;
2
3use lingon::Game;
4use lingon::random::{Distribute, NoDice, Uniform};
5use lingon::renderer::{Rect, Sprite, Tint, Transform};
6use std::path::PathBuf;
7use std::rc::Rc;
8use std::sync::Mutex;
9use std::sync::atomic::{AtomicU32, Ordering};
10use sylt_common::error::RuntimeError;
11use sylt_common::{RuntimeContext, Type, Value};
12
13// Errors are important, they should be easy to write!
14macro_rules! error {
15    ( $name:expr, $( $fmt:expr ),* ) => {
16        Err(RuntimeError::ExternError($name.to_string(), format!( $( $fmt ),* )))
17    }
18}
19
20fn unpack_int_int_tuple(value: &Value) -> (i64, i64) {
21    use Value::{Int, Tuple};
22    if let Tuple(tuple) = value {
23        if let (Some(Int(w)), Some(Int(h))) = (tuple.get(0), tuple.get(1)) {
24            return (*w, *h);
25        }
26    };
27    unreachable!("Expected tuple (int, int) but got '{:?}'", value);
28}
29
30fn parse_dist(name: &str) -> Option<Box<dyn lingon::random::Distribute>> {
31    use lingon::random::*;
32
33    Some(match name {
34        "Square" => Box::new(Square),
35        "ThreeDice" => Box::new(ThreeDice),
36        "TwoDice" => Box::new(TwoDice),
37        "Uniform" => Box::new(Uniform),
38        "NoDice" => Box::new(NoDice),
39        _ => {
40            return None;
41        }
42    })
43}
44
45std::thread_local! {
46    // Thread locals are allowed to reference each other as long as they're not infinitely recursive.
47    static GAME_WINDOW_SIZE: (AtomicU32, AtomicU32) = (AtomicU32::new(500), AtomicU32::new(500));
48    static PARTICLES: Mutex<Vec<lingon::renderer::ParticleSystem>> = Mutex::new(Vec::new());
49    static GAME: Mutex<Game<std::string::String>> = {
50        let (width, height) = GAME_WINDOW_SIZE.with(|(width, height)| (width.load(Ordering::Relaxed), height.load(Ordering::Relaxed)));
51        Mutex::new(Game::new("Lingon - Sylt", width, height))
52    }
53}
54
55sylt_macro::extern_function!(
56    "sylt_std::lingon"
57    l_update
58    "Updates the engine. Needs to be called once per frame"
59    [] -> Type::Void => {
60        GAME.with(|game| game.lock().unwrap().update());
61        Ok(Nil)
62    },
63);
64
65sylt_macro::extern_function!(
66    "sylt_std::lingon"
67    l_render
68    "Draws all render calls to the screen. Needs to be called once per frame"
69    [] -> Type::Void => {
70        GAME.with(|game| { game.lock().unwrap().draw().unwrap(); });
71        Ok(Nil)
72    },
73);
74
75sylt_macro::extern_function!(
76    "sylt_std::lingon"
77    l_window_size
78    "Returns the size of the game window"
79    [] -> Type::Tuple(vec![Type::Int, Type::Int]) => {
80        let (x, y) = GAME.with(|game| game.lock().unwrap().window_size());
81        Ok(Value::Tuple(Rc::new(vec![Value::Int(x as i64), Value::Int(y as i64)])))
82    },
83);
84
85sylt_macro::extern_function!(
86    "sylt_std::lingon"
87    l_set_window_size
88    "Sets the dimension of the game window"
89    [Two(Int(new_width), Int(new_height))] -> Type::Void => {
90        let new_width = *new_width as u32;
91        let new_height = *new_height as u32;
92        GAME_WINDOW_SIZE.with(|(width, height)| {
93            width.store(new_width, Ordering::Release);
94            height.store(new_height, Ordering::Release);
95        });
96        GAME.with(|game| game.lock().unwrap().set_window_size(new_width, new_height).unwrap());
97        Ok(Value::Nil)
98    },
99);
100
101sylt_macro::extern_function!(
102    "sylt_std::lingon"
103    l_set_window_icon
104    "Sets the window icon of the game window"
105    [One(String(path))] -> Type::Void => {
106        GAME.with(|game| game.lock().unwrap().set_window_icon(path.as_ref()));
107        Ok(Nil)
108    },
109);
110
111#[rustfmt::skip]
112fn l_gfx_rect_internal(x: &f64, y: &f64, w: &f64, h: &f64, rot: &f64, r: &f64, g: &f64, b: &f64, a: &f64) {
113    let mut rect = Rect::new();
114    rect.at(*x as f32, *y as f32);
115    rect.scale(*w as f32, *h as f32);
116    rect.angle(*rot as f32);
117    rect.rgba(*r as f32, *g as f32, *b as f32, *a as f32);
118
119    GAME.with(|game| game.lock().unwrap().renderer.push(rect));
120}
121
122sylt_macro::extern_function!(
123    "sylt_std::lingon"
124    l_gfx_rect
125    "Draws a rectangle on the screen, in many different ways"
126    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h))]
127    -> Type::Void => {
128        // x, y, w, h
129        l_gfx_rect_internal(x, y, w, h, &0.0, &1.0, &1.0, &1.0, &1.0);
130        Ok(Nil)
131    },
132    [Two(Float(x), Float(y)), Two(Float(w), Float(h))]
133    -> Type::Void => {
134        // (x, y), (w, h)
135        l_gfx_rect_internal(x, y, w, h, &0.0, &1.0, &1.0, &1.0, &1.0);
136        Ok(Nil)
137    },
138    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), Three(Float(r), Float(g), Float(b))]
139    -> Type::Void => {
140        // x, y, w, h, (r, g, b)
141        l_gfx_rect_internal(x, y, w, h, &0.0, r, g, b, &1.0);
142        Ok(Nil)
143    },
144    [Two(Float(x), Float(y)), Two(Float(w), Float(h)), Three(Float(r), Float(g), Float(b))]
145    -> Type::Void => {
146        // (x, y), (w, h), (r, g, b)
147        l_gfx_rect_internal(x, y, w, h, &0.0, r, g, b, &1.0);
148        Ok(Nil)
149    },
150    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), Four(Float(r), Float(g), Float(b), Float(a))]
151    -> Type::Void => {
152        // x, y, w, h, (r, g, b, a)
153        l_gfx_rect_internal(x, y, w, h, &0.0, r, g, b, a);
154        Ok(Nil)
155    },
156    [Two(Float(x), Float(y)), Two(Float(w), Float(h)), Four(Float(r), Float(g), Float(b), Float(a))]
157    -> Type::Void => {
158        // (x, y), (w, h), (r, g, b, a)
159        l_gfx_rect_internal(x, y, w, h, &0.0, r, g, b, a);
160        Ok(Nil)
161    },
162
163    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot))]
164    -> Type::Void => {
165        // x, y, w, h
166        l_gfx_rect_internal(x, y, w, h, rot, &1.0, &1.0, &1.0, &1.0);
167        Ok(Nil)
168    },
169    [Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot))]
170    -> Type::Void => {
171        // (x, y), (w, h)
172        l_gfx_rect_internal(x, y, w, h, rot, &1.0, &1.0, &1.0, &1.0);
173        Ok(Nil)
174    },
175    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot)), Three(Float(r), Float(g), Float(b))]
176    -> Type::Void => {
177        // x, y, w, h, (r, g, b)
178        l_gfx_rect_internal(x, y, w, h, rot, r, g, b, &1.0);
179        Ok(Nil)
180    },
181    [Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot)), Three(Float(r), Float(g), Float(b))]
182    -> Type::Void => {
183        // (x, y), (w, h), (r, g, b)
184        l_gfx_rect_internal(x, y, w, h, rot, r, g, b, &1.0);
185        Ok(Nil)
186    },
187    [One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot)), Four(Float(r), Float(g), Float(b), Float(a))]
188    -> Type::Void => {
189        // x, y, w, h, (r, g, b, a)
190        l_gfx_rect_internal(x, y, w, h, rot, r, g, b, a);
191        Ok(Nil)
192    },
193    [Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot)), Four(Float(r), Float(g), Float(b), Float(a))]
194    -> Type::Void => {
195        // (x, y), (w, h), (r, g, b, a)
196        l_gfx_rect_internal(x, y, w, h, rot, r, g, b, a);
197        Ok(Nil)
198    },
199);
200
201#[rustfmt::skip]
202fn l_gfx_sprite_internal(sprite: &i64, x: &f64, y: &f64, w: &f64, h: &f64, gx: &i64, gy: &i64, rot: &f64, r: &f64, g: &f64, b: &f64, a: &f64) -> Value {
203    let sprite = GAME.with(|game| game.lock().unwrap().renderer.sprite_sheets[*sprite as usize].grid(*gx as usize, *gy as usize));
204    let mut sprite = Sprite::new(sprite);
205    sprite.at(*x as f32, *y as f32).scale(*w as f32, *h as f32);
206    sprite.angle(*rot as f32);
207    sprite.rgba(*r as f32, *g as f32, *b as f32, *a as f32);
208    GAME.with(|game| game.lock().unwrap().renderer.push(sprite));
209    Value::Nil
210}
211
212sylt_macro::extern_function!(
213    "sylt_std::lingon"
214    l_gfx_sprite
215    "Draws a sprite on the screen, in many different ways.
216     Note that the first argument is a sprite id from <a href='#l_load_image'>l_load_image</a>"
217    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h))] -> Type::Void => {
218        // id, (gx, gy), (x), (y), (w), (h)
219        if name.as_ref() != "image" {
220            return error!("l_gfx_sprite", "Expected a sprite ID");
221        }
222        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, &1.0, &1.0, &1.0, &1.0))
223    },
224    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h))] -> Type::Void => {
225        // id, (gx, gy), (x, y), (w, h)
226        if name.as_ref() != "image" {
227            return error!("l_gfx_sprite", "Expected a sprite ID");
228        }
229        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, &1.0, &1.0, &1.0, &1.0))
230    },
231    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
232        // id, (gx, gy), (x), (y), (w), (h), (r, g, b)
233        if name.as_ref() != "image" {
234            return error!("l_gfx_sprite", "Expected a sprite ID")
235        }
236        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, r, g, b, &1.0))
237    },
238    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
239        // id, (gx, gy), (x, y), (w, h), (r, g, b)
240        if name.as_ref() != "image" {
241            return error!("l_gfx_sprite", "Expected a sprite ID")
242        }
243        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, r, g, b, &1.0))
244    },
245    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
246        // id, (gx, gy), (x), (y), (w), (h), (r, g, b, a)
247        if name.as_ref() != "image" {
248            return error!("l_gfx_sprite", "Expected a sprite ID")
249        }
250        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, r, g, b, a))
251    },
252    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
253        // id, (gx, gy), (x, y), (w, h), (r, g, b, a)
254        if name.as_ref() != "image" {
255            return error!("l_gfx_sprite", "Expected a sprite ID")
256        }
257        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, &0.0, r, g, b, a))
258    },
259    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot))] -> Type::Void => {
260        // id, (gx, gy), (x), (y), (w), (h), (rot)
261        if name.as_ref() != "image" {
262            return error!("l_gfx_sprite", "Expected a sprite ID");
263        }
264        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, &1.0, &1.0, &1.0, &1.0))
265    },
266    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot))] -> Type::Void => {
267        // id, (gx, gy), (x, y), (w, h), (rot)
268        if name.as_ref() != "image" {
269            return error!("l_gfx_sprite", "Expected a sprite ID");
270        }
271        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, &1.0, &1.0, &1.0, &1.0))
272    },
273    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
274        // id, (gx, gy), (x), (y), (w), (h), (rot), (r, g, b)
275        if name.as_ref() != "image" {
276            return error!("l_gfx_sprite", "Expected a sprite ID")
277        }
278        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, r, g, b, &1.0))
279    },
280    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
281        // id, (gx, gy), (x, y), (w, h), (rot), (r, g, b)
282        if name.as_ref() != "image" {
283            return error!("l_gfx_sprite", "Expected a sprite ID")
284        }
285        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, r, g, b, &1.0))
286    },
287    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), One(Float(x)), One(Float(y)), One(Float(w)), One(Float(h)), One(Float(rot)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
288        // id, (gx, gy), (x), (y), (w), (h), (rot), (r, g, b, a)
289        if name.as_ref() != "image" {
290            return error!("l_gfx_sprite", "Expected a sprite ID")
291        }
292        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, r, g, b, a))
293    },
294    [Two(String(name), Int(sprite)), Two(Int(gx), Int(gy)), Two(Float(x), Float(y)), Two(Float(w), Float(h)), One(Float(rot)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
295        // id, (gx, gy), (x, y), (w, h), (rot), (r, g, b, a)
296        if name.as_ref() != "image" {
297            return error!("l_gfx_sprite", "Expected a sprite ID")
298        }
299        Ok(l_gfx_sprite_internal(sprite, x, y, w, h, gx, gy, rot, r, g, b, a))
300    },
301);
302
303sylt_macro::extern_function!(
304    "sylt_std::lingon"
305    l_gfx_camera_at
306    "Gives out the position of the camera"
307    [] -> Type::Tuple(vec![Type::Float, Type::Float]) => {
308        GAME.with(|game| {
309            let camera = &mut game.lock().unwrap().renderer.camera;
310            let x = *camera.x_mut();
311            let y = *camera.y_mut();
312            Ok(Tuple(Rc::new(vec![Float(x as f64), Float(y as f64)])))
313        })
314    },
315);
316
317sylt_macro::extern_function!(
318    "sylt_std::lingon"
319    l_gfx_camera_place
320    "Positions the camera at a specific point in space"
321    [Two(Float(x), Float(y))] -> Type::Void => {
322        GAME.with(|game| { game.lock().unwrap().renderer.camera.at(*x as f32, *y as f32); });
323        Ok(Nil)
324    },
325    [One(Float(x)), One(Float(y))] -> Type::Void => {
326        GAME.with(|game| { game.lock().unwrap().renderer.camera.at(*x as f32, *y as f32); });
327        Ok(Nil)
328    },
329);
330
331sylt_macro::extern_function!(
332    "sylt_std::lingon"
333    l_gfx_camera_angle
334    "Sets the angle of the camera - in absolute terms"
335    [One(Float(angle))] -> Type::Void => {
336        GAME.with(|game| { game.lock().unwrap().renderer.camera.angle(*angle as f32); });
337        Ok(Nil)
338    },
339);
340
341sylt_macro::extern_function!(
342    "sylt_std::lingon"
343    l_gfx_camera_rotate
344    "Rotates the camera - relative to the current rotation"
345    [One(Float(by))] -> Type::Void => {
346        GAME.with(|game| { game.lock().unwrap().renderer.camera.rotate(*by as f32); });
347        Ok(Nil)
348    },
349);
350
351sylt_macro::extern_function!(
352    "sylt_std::lingon"
353    l_gfx_camera_set_zoom
354    "Specifies the zoom level"
355    [One(Float(to))] -> Type::Void => {
356        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale(*to as f32, *to as f32); });
357        Ok(Nil)
358    },
359    [One(Float(sx)), One(Float(sy))] -> Type::Void => {
360        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale(*sx as f32, *sy as f32); });
361        Ok(Nil)
362    },
363    [Two(Float(sx), Float(sy))] -> Type::Void => {
364        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale(*sx as f32, *sy as f32); });
365        Ok(Nil)
366    },
367);
368
369sylt_macro::extern_function!(
370    "sylt_std::lingon"
371    l_gfx_camera_zoom_by
372    "Zoomes relative to the current zoom level"
373    [One(Float(to))] -> Type::Void => {
374        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale_by(*to as f32, *to as f32); });
375        Ok(Nil)
376    },
377    [One(Float(sx)), One(Float(sy))] -> Type::Void => {
378        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale_by(*sx as f32, *sy as f32); });
379        Ok(Nil)
380    },
381    [Two(Float(sx), Float(sy))] -> Type::Void => {
382        GAME.with(|game| { game.lock().unwrap().renderer.camera.scale_by(*sx as f32, *sy as f32); });
383        Ok(Nil)
384    },
385);
386
387sylt_macro::extern_function!(
388    "sylt_std::lingon"
389    l_gfx_particle_new
390    "Creates a new particle system. Note specially the return type. Don't edit the return value."
391    [] -> Type::Tuple(vec![Type::String, Type::Int]) => {
392        let slot = PARTICLES.with(|ps| {
393            let mut ps = ps.lock().unwrap();
394            let slot = ps.len();
395            ps.push(lingon::renderer::ParticleSystem::new());
396            slot
397        });
398        Ok(Tuple(Rc::new(vec![sylt_str("particle"), Int(slot as i64)])))
399    },
400);
401
402sylt_macro::extern_function!(
403    "sylt_std::lingon"
404    l_gfx_particle_spawn
405    "Spawn a new particle for the given particle system"
406    [Two(String(name), Int(system))] -> Type::Void => {
407        if name.as_ref() != "particle" {
408            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
409        }
410        PARTICLES.with(|ps| {
411            ps.lock().unwrap()[*system as usize].spawn();
412        });
413        Ok(Nil)
414    },
415    [Two(String(name), Int(system)), One(Int(amount))] -> Type::Void => {
416        if name.as_ref() != "particle" {
417            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
418        }
419        PARTICLES.with(|ps| {
420            ps.lock().unwrap()[*system as usize].spawn_many(*amount as u32);
421        });
422        Ok(Nil)
423    },
424);
425
426sylt_macro::extern_function!(
427    "sylt_std::lingon"
428    l_gfx_particle_update
429    "Updates the particle system, stepping it forward in time"
430    [Two(String(name), Int(system)), One(Float(delta))] -> Type::Void => {
431        if name.as_ref() != "particle" {
432            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
433        }
434        PARTICLES.with(|ps| {
435            ps.lock().unwrap()[*system as usize].update(*delta as f32);
436        });
437        Ok(Nil)
438    },
439);
440
441sylt_macro::extern_function!(
442    "sylt_std::lingon"
443    l_gfx_particle_render
444    "Renders the particle system, has to be called each frame"
445    [Two(String(name), Int(system))] -> Type::Void => {
446        if name.as_ref() != "particle" {
447            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
448        }
449        PARTICLES.with(|ps| {
450            GAME.with(|game| game.lock().unwrap().renderer.push_particle_system(&ps.lock().unwrap()[*system as usize]));
451        });
452        Ok(Nil)
453    },
454);
455
456sylt_macro::extern_function!(
457    "sylt_std::lingon"
458    l_gfx_particle_add_sprite
459    "Adds a sprite to the particle system as an alternative when spawning. If nothing is added it's colored rectangles all the way"
460    [Two(String(s_name), Int(system)), Two(String(sp_name), Int(sprite)), Two(Int(gx), Int(gy))] -> Type::Void => {
461        if s_name.as_ref() != "particle" {
462            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
463        }
464        if sp_name.as_ref() != "image" {
465            return error!("l_gfx_sprite", "Expected a sprite ID");
466        }
467
468        let sprite = GAME.with(|game| game.lock().unwrap().renderer.sprite_sheets[*sprite as usize].grid(*gx as usize, *gy as usize));
469        PARTICLES.with(|ps| {
470            ps.lock().unwrap()[*system as usize].sprites.push(sprite);
471        });
472        Ok(Nil)
473    },
474);
475
476macro_rules! particle_prop {
477    { $name:ident, $prop:ident } => {
478        sylt_macro::extern_function!(
479            "sylt_std::lingon"
480            $name
481            "Sets the given particle prop"
482            [Two(String(name), Int(system)), Two(Float(lo), Float(hi))] -> Type::Void => {
483                if name.as_ref() != "particle" {
484                    return error!("l_gfx_particle_spawn", "Expected a particle system ID");
485                }
486                let prop = lingon::random::RandomProperty::new(*lo as f32, *hi as f32, Box::new(Uniform));
487                PARTICLES.with(|ps| {
488                    ps.lock().unwrap()[*system as usize].$prop = prop;
489                });
490                Ok(Nil)
491            },
492            [Two(String(name), Int(system)), Two(Float(lo), Float(hi)), One(String(dist))] -> Type::Void => {
493                if name.as_ref() != "particle" {
494                    return error!("l_gfx_particle_spawn", "Expected a particle system ID");
495                }
496                if let Some(dist) = parse_dist(dist) {
497                    let prop = lingon::random::RandomProperty::new(*lo as f32, *hi as f32, dist);
498                    PARTICLES.with(|ps| {
499                        ps.lock().unwrap()[*system as usize].$prop = prop;
500                    });
501                    Ok(Nil)
502                } else {
503                    error!(stringify!($name), "Failed to parse distribution '{}'", dist)
504                }
505            },
506        );
507    };
508}
509
510particle_prop! { l_gfx_particle_x, x }
511particle_prop! { l_gfx_particle_y, y }
512
513particle_prop! { l_gfx_particle_lifetime, lifetime }
514
515particle_prop! { l_gfx_particle_vel_angle, vel_angle }
516particle_prop! { l_gfx_particle_vel_magnitude, vel_magnitude }
517
518particle_prop! { l_gfx_particle_acc_angle, acc_angle }
519particle_prop! { l_gfx_particle_acc_magnitude, acc_magnitude }
520particle_prop! { l_gfx_particle_drag, drag }
521
522particle_prop! { l_gfx_particle_angle, angle }
523particle_prop! { l_gfx_particle_angle_velocity, angle_velocity }
524particle_prop! { l_gfx_particle_angle_drag, angle_drag }
525
526particle_prop! { l_gfx_particle_start_sx, start_sx }
527particle_prop! { l_gfx_particle_start_sy, start_sy }
528particle_prop! { l_gfx_particle_end_sx, end_sx }
529particle_prop! { l_gfx_particle_end_sy, end_sy }
530
531particle_prop! { l_gfx_particle_start_red, start_red }
532particle_prop! { l_gfx_particle_start_green, start_green }
533particle_prop! { l_gfx_particle_start_blue, start_blue }
534particle_prop! { l_gfx_particle_start_alpha, start_alpha }
535
536particle_prop! { l_gfx_particle_end_red, end_red }
537particle_prop! { l_gfx_particle_end_green, end_green }
538particle_prop! { l_gfx_particle_end_blue, end_blue }
539particle_prop! { l_gfx_particle_end_alpha, end_alpha }
540
541sylt_macro::extern_function!(
542    "sylt_std::lingon"
543    l_gfx_particle_start_color
544    "Sets the spawn color of the particles"
545    [Two(String(name), Int(system)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
546        if name.as_ref() != "particle" {
547            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
548        }
549        let r = lingon::random::RandomProperty::new(*r as f32, *r as f32, Box::new(NoDice));
550        let g = lingon::random::RandomProperty::new(*g as f32, *g as f32, Box::new(NoDice));
551        let b = lingon::random::RandomProperty::new(*b as f32, *b as f32, Box::new(NoDice));
552        PARTICLES.with(|ps| {
553            let mut ps = ps.lock().unwrap();
554            ps[*system as usize].start_red = r;
555            ps[*system as usize].start_green = g;
556            ps[*system as usize].start_blue = b;
557        });
558        Ok(Nil)
559    },
560    [Two(String(name), Int(system)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
561        if name.as_ref() != "particle" {
562            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
563        }
564        let r = lingon::random::RandomProperty::new(*r as f32, *r as f32, Box::new(NoDice));
565        let g = lingon::random::RandomProperty::new(*g as f32, *g as f32, Box::new(NoDice));
566        let b = lingon::random::RandomProperty::new(*b as f32, *b as f32, Box::new(NoDice));
567        let a = lingon::random::RandomProperty::new(*a as f32, *a as f32, Box::new(NoDice));
568        PARTICLES.with(|ps| {
569            let mut ps = ps.lock().unwrap();
570            ps[*system as usize].start_red = r;
571            ps[*system as usize].start_green = g;
572            ps[*system as usize].start_blue = b;
573            ps[*system as usize].start_alpha = a;
574        });
575        Ok(Nil)
576    },
577);
578
579sylt_macro::extern_function!(
580    "sylt_std::lingon"
581    l_gfx_particle_end_color
582    "Sets the spawn color of the particles"
583    [Two(String(name), Int(system)), Three(Float(r), Float(g), Float(b))] -> Type::Void => {
584        if name.as_ref() != "particle" {
585            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
586        }
587        let r = lingon::random::RandomProperty::new(*r as f32, *r as f32, Box::new(NoDice));
588        let g = lingon::random::RandomProperty::new(*g as f32, *g as f32, Box::new(NoDice));
589        let b = lingon::random::RandomProperty::new(*b as f32, *b as f32, Box::new(NoDice));
590        PARTICLES.with(|ps| {
591            let mut ps = ps.lock().unwrap();
592            ps[*system as usize].end_red = r;
593            ps[*system as usize].end_green = g;
594            ps[*system as usize].end_blue = b;
595        });
596        Ok(Nil)
597    },
598    [Two(String(name), Int(system)), Four(Float(r), Float(g), Float(b), Float(a))] -> Type::Void => {
599        if name.as_ref() != "particle" {
600            return error!("l_gfx_particle_spawn", "Expected a particle system ID");
601        }
602        let r = lingon::random::RandomProperty::new(*r as f32, *r as f32, Box::new(NoDice));
603        let g = lingon::random::RandomProperty::new(*g as f32, *g as f32, Box::new(NoDice));
604        let b = lingon::random::RandomProperty::new(*b as f32, *b as f32, Box::new(NoDice));
605        let a = lingon::random::RandomProperty::new(*a as f32, *a as f32, Box::new(NoDice));
606        PARTICLES.with(|ps| {
607            let mut ps = ps.lock().unwrap();
608            ps[*system as usize].end_red = r;
609            ps[*system as usize].end_green = g;
610            ps[*system as usize].end_blue = b;
611            ps[*system as usize].end_alpha = a;
612        });
613        Ok(Nil)
614    },
615);
616
617sylt_macro::extern_function!(
618    "sylt_std::lingon"
619    l_delta
620    "The time since last the frame in seconds"
621    [] -> Type::Float => {
622        let delta = GAME.with(|game| game.lock().unwrap().delta() as f64);
623        Ok(Float(delta))
624    },
625);
626
627sylt_macro::extern_function!(
628    "sylt_std::lingon"
629    l_time
630    "An absolute time messurement, but the start time is ill defined"
631    [] -> Type::Float => {
632        let time = GAME.with(|game| game.lock().unwrap().total_time() as f64);
633        Ok(Float(time))
634    },
635);
636
637sylt_macro::extern_function!(
638    "sylt_std::lingon"
639    l_random
640    "Returns a uniformly sampled random float between 0 and 1"
641    [] -> Type::Float => {
642        Ok(Float(Uniform.sample().into()))
643    },
644);
645
646sylt_macro::extern_function!(
647    "sylt_std::lingon"
648    l_random_range
649    "Returns a randomized integer in the given range"
650    [One(Int(lo)), One(Int(hi))] -> Type::Int => {
651        Ok(Int(*lo + (Uniform.sample() * ((hi - lo + 1) as f32)) as i64))
652    },
653    [Two(Int(lo), Int(hi))] -> Type::Int => {
654        Ok(Int(*lo + (Uniform.sample() * ((hi - lo + 1) as f32)) as i64))
655    },
656    [One(Float(lo)), One(Float(hi))] -> Type::Float => {
657        Ok(Float(*lo + Uniform.sample() as f64 * (hi - lo)))
658    },
659    [Two(Float(lo), Float(hi))] -> Type::Float => {
660        Ok(Float(*lo + Uniform.sample() as f64 * (hi - lo)))
661    },
662);
663
664sylt_macro::extern_function!(
665    "sylt_std::lingon"
666    l_bind_key
667    "Binds a keyboard key to an input name"
668    [One(String(key)), One(String(name))] -> Type::Void => {
669        let key = if let Some(key) = Keycode::from_name(key) {
670            key
671        } else {
672            return error!("l_bind_key", "'{}' is an invalid key", key);
673        };
674
675        use lingon::input::{Device::Key, Keycode};
676        GAME.with(|game| game.lock().unwrap().input.bind(Key(key), std::string::String::clone(name)));
677
678        Ok(Nil)
679    },
680);
681
682sylt_macro::extern_function!(
683    "sylt_std::lingon"
684    l_bind_quit
685    "Binds the windows quit action (pressing the X in the corner) - plus points if you make it jump"
686    [One(String(name))] -> Type::Void => {
687        use lingon::input::Device::Quit;
688        GAME.with(|game| game.lock().unwrap().input.bind(Quit, std::string::String::clone(name)));
689        Ok(Nil)
690    },
691);
692
693sylt_macro::extern_function!(
694    "sylt_std::lingon"
695    l_bind_button
696    "Binds a controller button to an input name"
697    [One(Int(controller)), One(String(button)), One(String(name))] -> Type::Void => {
698        use lingon::input::{Device, Button};
699        let button = if let Some(button) = Button::from_string(button) {
700            button
701        } else {
702            return error!("l_bind_button", "'{}' is an invalid button", button);
703        };
704
705        GAME.with(|game| game.lock().unwrap().input.bind(Device::Button(*controller as u32, button), std::string::String::clone(name)));
706        Ok(Nil)
707    },
708);
709
710sylt_macro::extern_function!(
711    "sylt_std::lingon"
712    l_bind_axis
713    "Binds a controller axis to an input name"
714    [One(Int(controller)), One(String(axis)), One(String(name))] -> Type::Void => {
715        use lingon::input::{Device, Axis};
716        let axis = if let Some(axis) = Axis::from_string(axis) {
717            axis
718        } else {
719            return error!("l_bind_axis", "'{}' is an invalid axis", axis);
720        };
721
722        GAME.with(|game| game.lock().unwrap().input.bind(Device::Axis(*controller as u32, axis), std::string::String::clone(name)));
723        Ok(Nil)
724    },
725);
726
727sylt_macro::extern_function!(
728    "sylt_std::lingon"
729    l_bind_mouse
730    "Binds a mouse button, allows the following keys: ['left', 'middle', 'right', 'x1', 'x2']"
731    [One(String(button)), One(String(name))] -> Type::Void => {
732        use lingon::input::{Device::Mouse, MouseButton::*};
733        let button = match button.as_str() {
734            "left" => Left,
735            "middle" => Middle,
736            "right" => Right,
737            "x1" => X1,
738            "x2" => X2,
739            _ => { return error!("l_bind_mouse", "'{}' is an invalid mouse button", button); }
740        };
741
742        GAME.with(|game| game.lock().unwrap().input.bind(Mouse(button), std::string::String::clone(name)));
743        Ok(Nil)
744    },
745);
746
747sylt_macro::extern_function!(
748    "sylt_std::lingon"
749    l_input_down
750    "Returns true if the input name is down this frame, e.g. pressed"
751    [One(String(name))] -> Type::Bool => {
752        Ok(Bool(GAME.with(|game| game.lock().unwrap().input.down(std::string::String::clone(name)))))
753    },
754);
755
756sylt_macro::extern_function!(
757    "sylt_std::lingon"
758    l_input_up
759    "Returns true if the input name is up this frame, e.g. not pressed"
760    [One(String(name))] -> Type::Bool => {
761        Ok(Bool(GAME.with(|game| game.lock().unwrap().input.up(std::string::String::clone(name)))))
762    },
763);
764
765sylt_macro::extern_function!(
766    "sylt_std::lingon"
767    l_input_pressed
768    "Returns true if the input name started being down this frame, e.g. a tap"
769    [One(String(name))] -> Type::Bool => {
770        Ok(Bool(GAME.with(|game| game.lock().unwrap().input.pressed(std::string::String::clone(name)))))
771    },
772);
773
774sylt_macro::extern_function!(
775    "sylt_std::lingon"
776    l_input_released
777    "Returns true if the input name started being up this frame, e.g. a release"
778    [One(String(name))] -> Type::Bool => {
779        Ok(Bool(GAME.with(|game| game.lock().unwrap().input.released(std::string::String::clone(name)))))
780    },
781);
782
783sylt_macro::extern_function!(
784    "sylt_std::lingon"
785    l_input_value
786    "Returns the float representation of the input name, usefull for reading controller inputs"
787    [One(String(name))] -> Type::Float => {
788        Ok(Float(GAME.with(|game| game.lock().unwrap().input.value(std::string::String::clone(name)) as f64)))
789    },
790);
791
792sylt_macro::extern_function!(
793    "sylt_std::lingon"
794    l_audio_play
795    "Plays an audio source. Expects the first value to be a
796     return value from <a href='#l_load_audio'>l_load_audio</a>"
797    [Two(String(name), Int(sound)),
798     One(Bool(looping)),
799     One(Float(gain)),
800     One(Float(pitch)),
801    ] -> Type::Void => {
802        if name.as_ref() != "audio" {
803            return error!("l_audio_play", "");
804        }
805
806        GAME.with(|game| {
807            let mut game = game.lock().unwrap();
808            let sound = &game.assets[unsafe { lingon::asset::AudioAssetID::from_usize(*sound as usize) }];
809            let source = lingon::audio::AudioSource::new(sound)
810                .looping(*looping)
811                .gain(*gain as f32)
812                .pitch(*pitch as f32);
813            game.audio.lock().play(source);
814        });
815
816        Ok(Nil)
817    },
818    [Two(String(name), Int(sound)),
819     One(Bool(looping)),
820     Two(Float(gain), Float(gain_variance)),
821     Two(Float(pitch), Float(pitch_variance)),
822    ] -> Type::Void => {
823        if name.as_ref() != "audio" {
824            return error!("l_audio_play", "");
825        }
826
827        GAME.with(|game| {
828            let mut game = game.lock().unwrap();
829            let sound = &game.assets[unsafe { lingon::asset::AudioAssetID::from_usize(*sound as usize) }];
830            let source = lingon::audio::AudioSource::new(sound)
831                .looping(*looping)
832                .gain(*gain as f32).gain_variance(*gain_variance as f32)
833                .pitch(*pitch as f32).pitch_variance(*pitch_variance as f32);
834            game.audio.lock().play(source);
835        });
836        Ok(Nil)
837    },
838);
839
840sylt_macro::extern_function!(
841    "sylt_std::lingon"
842    l_audio_master_gain
843    "Controls the master gain of the audio mixer"
844    [One(Float(gain))] -> Type::Void => {
845        GAME.with(|game| { *game.lock().unwrap().audio.lock().gain_mut() = *gain as f32; });
846        Ok(Nil)
847    },
848);
849
850sylt_macro::extern_function!(
851    "sylt_std::lingon"
852    l_mouse
853    "Gets the current mouse position"
854    [] -> Type::Tuple(vec!(Type::Int, Type::Int)) => {
855        let mouse = GAME.with(|game| game.lock().unwrap().input.mouse());
856        Ok(Tuple(Rc::new(vec!(Int(mouse.0 as i64), Int(mouse.1 as i64)))))
857    },
858);
859
860sylt_macro::extern_function!(
861    "sylt_std::lingon"
862    l_mouse_rel
863    "Gets the relative mouse position since the last frame"
864    [] -> Type::Tuple(vec!(Type::Int, Type::Int)) => {
865        let mouse = GAME.with(|game| game.lock().unwrap().input.mouse_rel());
866        Ok(Tuple(Rc::new(vec!(Int(mouse.0 as i64), Int(mouse.1 as i64)))))
867    },
868);
869
870pub fn sylt_str(s: &str) -> Value {
871    Value::String(Rc::new(s.to_string()))
872}
873
874#[sylt_macro::sylt_doc(l_load_image, "Loads an image and turns it into a sprite sheet",
875  [One(String(path)), Two(Float, Float)] Type::Tuple)]
876#[sylt_macro::sylt_link(l_load_image, "sylt_std::lingon")]
877pub fn l_load_image<'t>(ctx: RuntimeContext<'t>) -> Result<Value, RuntimeError> {
878    let values = ctx.machine.stack_from_base(ctx.stack_base);
879    match (values.as_ref(), ctx.typecheck) {
880        ([Value::String(path), tilesize], false) => {
881            GAME.with(|game| {
882                let mut game = game.lock().unwrap();
883                let path = PathBuf::from(path.as_ref());
884                let image = game.assets.load_image(path);
885                let image = game.assets[image].clone();
886
887                let dim = unpack_int_int_tuple(tilesize);
888                let slot = game
889                    .renderer
890                    .add_sprite_sheet(image, (dim.0 as usize, dim.1 as usize));
891                Ok(Value::Tuple(Rc::new(vec![sylt_str("image"), Value::Int(slot as i64)])))
892            })
893        }
894        ([Value::String(_), tilesize], true) => {
895            unpack_int_int_tuple(tilesize);
896            Ok(Value::Tuple(Rc::new(vec![sylt_str("image"), Value::Int(0)])))
897        }
898        (values, _) => Err(RuntimeError::ExternTypeMismatch(
899            "l_load_image".to_string(),
900            values.iter().map(Type::from).collect(),
901        )),
902    }
903}
904
905#[sylt_macro::sylt_doc(l_load_audio,
906  "Loads a sound and lets you play it using <a href='l_audio_play'>l_audio_play</a>",
907  [One(String(path))] Type::Tuple)]
908#[sylt_macro::sylt_link(l_load_audio, "sylt_std::lingon")]
909pub fn l_load_audio<'t>(ctx: RuntimeContext<'t>) -> Result<Value, RuntimeError> {
910    let values = ctx.machine.stack_from_base(ctx.stack_base);
911    match (values.as_ref(), ctx.typecheck) {
912        ([Value::String(path)], false) => {
913            let path = PathBuf::from(path.as_ref());
914            let audio = GAME.with(|game| game.lock().unwrap().assets.load_audio(path));
915            Ok(Value::Tuple(Rc::new(vec![sylt_str("audio"), Value::Int(*audio as i64)])))
916        }
917        ([Value::String(_)], true)
918            => Ok(Value::Tuple(Rc::new(vec![sylt_str("audio"), Value::Int(0)]))),
919        (values, _) => Err(RuntimeError::ExternTypeMismatch(
920            "l_load_image".to_string(),
921            values.iter().map(Type::from).collect(),
922        )),
923    }
924}
925
926sylt_macro::extern_function!(
927    "sylt_std::lingon"
928    l_set_text_input_enabled
929    ""
930    [One(Bool(b))] -> Type::Void => {
931        GAME.with(|game| game.lock().unwrap().input.set_text_input_enabled(*b));
932        Ok(Value::Nil)
933    },
934);
935
936#[sylt_macro::sylt_link(l_text_input_update, "sylt_std::lingon")]
937pub fn l_text_input_update<'t>(ctx: RuntimeContext<'t>) -> Result<Value, RuntimeError> {
938    if ctx.typecheck {
939        return Ok(Value::from(Type::Tuple(vec![Type::String, Type::Bool])));
940    }
941
942    let values = ctx.machine.stack_from_base(ctx.stack_base);
943    match values.as_ref() {
944        [Value::String(s)] => {
945            GAME.with(|game| {
946                let mut s = std::string::String::clone(s);
947                let found_return = game.lock().unwrap().input.text_input_update(&mut s);
948                Ok(Value::Tuple(Rc::new(vec![Value::String(Rc::new(s)), Value::Bool(found_return)])))
949            })
950        }
951        _ => unimplemented!(),
952    }
953}
954
955sylt_macro::sylt_link_gen!("sylt_std::lingon");