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
13macro_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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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");