-
Notifications
You must be signed in to change notification settings - Fork 20
/
allegro.js
1918 lines (1710 loc) · 67.7 KB
/
allegro.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// \file allegro.js
////////////////////////////////////////////
/// @name CONFIGURATION ROUTINES
//@{
/// Installs allegro.
/// This function must be called before anything else. It makes sure Date.now() exists.
function install_allegro()
{
if (!Date.now)
Date.now = function now() { return new Date().getTime(); };
log("Allegro installed!");
}
/// Wrapper for install_allegro.
function allegro_init()
{
install_allegro();
}
/// Inits Allegro and installs all subsystems.
/// Calls install_allegro(), install_mouse(), install_timer(), install_keyboard(), install_sound() and set_gfx_mode() with provided parameters
/// @param id drawing canvas id
/// @param w screen width in pixels
/// @param h screen height in pixels
/// @param menu set this to true to enable context menu
function allegro_init_all(id,w,h,menu,enable_keys)
{
install_allegro();
set_gfx_mode(id,w,h);
install_mouse(menu);
install_keyboard(enable_keys);
install_sound();
}
/// Macro to be placed after the end of main()
/// Calls main()
function END_OF_MAIN()
{
window.addEventListener("load", main);
}
//@}
////////////////////////////////////////////
/// @name MOUSE ROUTINES
//@{
/// Mouse button bitmask.
/// Each bit in the mask represents a separate mouse button state. If right mouse button is down, mouse_b value would be 4, 00100 in binary. Each bit represents one mouse button. use something like if (mouse_b&1) to check for separate buttons.
/// * Button 0 is LMB. (mouse_b&1)
/// * Button 1 is MMB / wheel. (mouse_b&2)
/// * Button 2 is RMB. (mouse_b&4)
var mouse_b = 0;
/// Same as mouse_b but only checks if a button was pressed last frame
/// Note that this only works inside loop()
var mouse_pressed = 0;
/// Same as mouse_b but only checks if a button was released last frame
/// Note that this only works inside loop()
var mouse_released = 0;
/// Mouse X position within the canvas.
var mouse_x = -1;
/// Mouse Y position within the canvas
var mouse_y = -1;
/// Mouse wheel position.
/// This might not work consistently across all browsers!
var mouse_z = -1;
/// Mouse mickey, X position since last loop().
/// Only works inside loop()
var mouse_mx = 0;
/// Mouse mickey, Y position since last loop().
/// Only works inside loop()
var mouse_my = 0;
/// Mouse mickey, wheel position since last loop().
/// Only works inside loop()
var mouse_mz = 0;
/// Checks if mouse was installed
var _mouse_installed = false;
/// last mouse x position
var _last_mouse_x = -1;
/// last mouse y position
var _last_mouse_y = -1;
/// last mouse wheel position
var _last_mouse_z = -1;
/// is context menu enabled?
var _menu = false;
/// Installs mouse handlers.
/// Must be called after set_gfx_mode() to be able to determine mouse position within the given canvas!
/// @param menu If true, context menu will be available on right click on canvas. Default is false.
function install_mouse(menu)
{
if (!canvas)
{
_error("You must call set_gfx_mode before install_mouse");
return -1;
}
if (_mouse_installed)
{
_allog("Mouse already installed");
return -1;
}
canvas.canvas.addEventListener('mouseup',_mouseup);
canvas.canvas.addEventListener('mousedown',_mousedown);
canvas.canvas.addEventListener('mousemove',_mousemove);
canvas.canvas.addEventListener('wheel',_mousewheel);
if (menu)
{
_menu_supress=true;
} else {
canvas.canvas.addEventListener('contextmenu',_mousemenu);
_menu_supress=false;
}
_mouse_installed = true;
log("Mouse installed!");
return 0;
}
/// Removes mouse handlers.
function remove_mouse()
{
if (!_mouse_installed)
{
_error("You must call install_mouse before remove_mouse");
return -1;
}
canvas.canvas.removeEventListener('mouseup',_mouseup);
canvas.canvas.removeEventListener('mousedown',_mousedown);
canvas.canvas.removeEventListener('mousemove',_mousemove);
canvas.canvas.removeEventListener('wheel',_mousewheel);
if (_menu_supress) canvas.canvas.removeEventListener('contextmenu',_mousemenu);
_mouse_installed = false;
log("Mouse removed!");
return 0;
}
/// Enables showing system mouse cursor over canvas
function show_mouse()
{
if (!_mouse_installed)
{
_error("You must call install_mouse before show_mouse");
return -1;
}
canvas.canvas.style.cursor = "auto";
return 0;
}
/// Disables system mouse cursor over canvas.
/// Use this if you would like to provide your own cursor bitmap
function hide_mouse()
{
if (!_mouse_installed)
{
_error("You must call install_mouse before hide_mouse");
return -1;
}
canvas.canvas.style.cursor = "none";
return 0;
}
/// Mouse context menu suppressor
function _mousemenu(e)
{
e.preventDefault();
}
/// mouse up event handler
function _mouseup(e)
{
mouse_b = mouse_b&~(1<<(e.which-1));
mouse_released = mouse_released|(1<<(e.which-1));
e.preventDefault();
}
/// mouse down event handler
function _mousedown(e)
{
mouse_b = mouse_b|(1<<(e.which-1));
mouse_pressed = mouse_pressed|(1<<(e.which-1));
e.preventDefault();
}
/// mouse move event handler
function _mousemove(e)
{
mouse_x = e.offsetX;
mouse_y = e.offsetY;
e.preventDefault();
}
/// mouse wheel event handler
function _mousewheel(e)
{
mouse_z += e.deltaY;
e.preventDefault();
}
//@}
////////////////////////////////////////////
/// @name TOUCH ROUTINES
//@{
/// Touch object, This is not a function.
/// This is not a function, it's the structure of touch object found in touch[] array and inside touch_pressed touch_released. You can retain the touch obect picked up from touch_pressed in your code, but remember to let go of the dead ones.
/// @param x,y current touch position
/// @param mx,my delta position (amount of pixels moved)
/// @param px,py previous touch position
/// @param sx,sy starting touch position
/// @param id touch id
/// @param age how many loops is the touch in
/// @param dead true when touch is released
function TOUCH_OBJECT(x,y,mx,my,px,py,sx,sy,id,age,dead) {}
/// is touch installed
var _touch_installed = false;
/// Array of TOUCH_OBJECTS holding the currently held touches
var touch = [];
/// Array of TOUCH_OBJECTS holding the just started touches
var touch_pressed = [];
/// Array of TOUCH_OBJECTS holding the just finished touches
var touch_released = [];
/// Installs touch support
/// Installs handlers for touch events. After calling this, touch* arrays will get populated with multitouch data maximum touch points depend on the device. Four is usually a safe option.
function install_touch()
{
if (!canvas)
{
_error("You must call set_gfx_mode before install_touch");
return -1;
}
if (_touch_installed)
{
_allog("Touch already installed");
return -1;
}
canvas.canvas.addEventListener("touchstart", _touchstart);
canvas.canvas.addEventListener("touchend", _touchend);
canvas.canvas.addEventListener("touchcancel", _touchend);
canvas.canvas.addEventListener("touchmove", _touchmove);
_touch_installed = true;
log("Touch installed!");
}
/// Removes touch support
/// Uninstalls handlers for touch events.
function remove_touch()
{
if (!canvas)
{
_error("You must call set_gfx_mode before install_touch");
return -1;
}
if (!_touch_installed)
{
_allog("Touch not installed");
return -1;
}
canvas.canvas.removeEventListener("touchstart", _touchstart);
canvas.canvas.removeEventListener("touchend", _touchend);
canvas.canvas.removeEventListener("touchcancel", _touchend);
canvas.canvas.removeEventListener("touchmove", _touchmove);
_touch_installed = false;
log("Touch removed!");
}
function _get_touch(id)
{
for(var c=0;c<touch.length;c++)
{
if (touch[c].id==id) return touch[c];
}
return null;
}
function _touchstart(e)
{
var rect = e.target.getBoundingClientRect();
for (var c=0;c<e.changedTouches.length;c++)
{
var point = e.changedTouches.item(c);
var t = {
sx:point.clientX-rect.left,
sx:point.clientY-rect.top,
mx:0,
my:0,
px:point.clientX-rect.left,
py:point.clientY-rect.top,
x:point.clientX-rect.left,
y:point.clientY-rect.top,
id:point.identifier ,
dead:false,
age:0
};
touch.push(t);
touch_pressed.push(t);
}
e.preventDefault();
}
function _touchend(e)
{
for (var c=0;c<e.changedTouches.length;c++)
{
var point = e.changedTouches.item(c);
var t = _get_touch(point.identifier );
if (t)
{
touch.splice(touch.indexOf(t),1);
touch_released.push(t);
t.dead=true;
}
}
e.preventDefault();
}
function _touchmove(e)
{
var rect = e.target.getBoundingClientRect();
for (var c=0;c<e.changedTouches.length;c++)
{
var point = e.changedTouches.item(c);
var t = _get_touch(point.identifier );
if (t)
{
var x = point.clientX-rect.left;
var y = point.clientY-rect.top;
t.mx+=t.x-x;
t.my+=t.y-y;
t.x=x;
t.y=y;
}
}
e.preventDefault();
}
//@}
////////////////////////////////////////////
/// @name TIMER ROUTINES
//@{
/// All downloadable objects
var _downloadables = [];
/// holds all currently installed timers
var _installed_timers = [];
/// looks up a timer by it's function on the list
function _timer_lookup(proc)
{
for(var c=0;c<_installed_timers.length;c++)
{
if (_installed_timers[c].timer==proc) return _installed_timers[c];
}
return -1;
}
/// Converts seconds to install_int_ex interval units
/// @param secs number of seconds
/// @return value converted to milliseconds
function SECS_TO_TIMER(secs) {return secs*1000;}
/// Converts milliseconds to install_int_ex interval units
/// @param msec number of milliseconds
/// @return value converted to milliseconds
function MSEC_TO_TIMER(msec) {return msec;}
/// Converts beats-per-second to install_int_ex interval units
/// @param bps number of beats per second
/// @return value converted to milliseconds
function BPS_TO_TIMER(bps) {return 1000/bps;}
/// Converts beats-per-minute to install_int_ex interval units
/// @param bpm number of beats per minute
/// @return value converted to milliseconds
function BPM_TO_TIMER(bpm) {return 60*1000/bpm;}
/// Does nothing.
function install_timer()
{
}
/// Unix time stamp!
/// Returns number of milliseconds since 1970 started.
function time()
{
return Date.now();
}
/// Installs interrupt function.
/// Installs a user timer handler, with the speed given as the number of milliseconds between ticks. This is the same thing as install_int_ex(proc, MSEC_TO_TIMER(speed)). Calling again this routine with the same timer handler as parameter allows you to adjust its speed.
/// @param procedure function to be called
/// @param speed execution interval in msec
function install_int(procedure,msec)
{
return install_int_ex(procedure,MSEC_TO_TIMER(msec));
}
/// Installs interrupt function.
/// With this one, you must use helper functions to set the interval in the second argument. The lowest interval is 1 msec, but you probably don't want to go below 17 msec. Suggested values are BPS_TO_TIMER(30) or BPS_TO_TIMER(60). It cannot be used to alter previously installed interrupt function as well.
/// * SECS_TO_TIMER(secs) - seconds
/// * MSEC_TO_TIMER(msec) - milliseconds (1/1000th)
/// * BPS_TO_TIMER(bps) - beats per second
/// * BPM_TO_TIMER(bpm) - beats per minute
/// @param procedure function to be called
/// @param speed execution interval
function install_int_ex(procedure,speed)
{
var timer_id = window.setInterval(procedure,speed);
_installed_timers.push({timer:procedure,id:timer_id});
log("Added insterrupt #" + timer_id + " at " + speed + "msec isntervals!");
}
/// registered loop procedure
var _loopproc;
/// Performs some loop tasks, such as cleaning up pressed[] and released[]
function _uberloop()
{
if (_mouse_installed)
{
mouse_mx = mouse_x - _last_mouse_x;
mouse_my = mouse_y - _last_mouse_y;
mouse_mz = mouse_z - _last_mouse_z;
}
_loopproc();
if (_keyboard_installed)
{
for (var c=0;c<0x80;c++)
{
pressed[c] = false;
released[c] = false;
}
}
if (_mouse_installed)
{
mouse_pressed = 0;
mouse_released = 0;
mouse_mx = 0;
mosue_my = 0;
mouse_mz = 0;
_last_mouse_x = mouse_x;
_last_mouse_y = mouse_y;
_last_mouse_z = mouse_z;
}
if (_touch_installed)
{
touch_released = [];
touch_pressed = [];
for(var c=0;c<touch.length;c++)
{
touch[c].mx=0;
touch[c].my=0;
touch[c].px=touch[c].x;
touch[c].py=touch[c].y;
touch[c].age++;
}
}
}
/// Game loop interrupt
/// Loop is the same as interrupt, except, it cannot be stopped once it's started. It's meant for game loop. remove_int() and remove_all_ints() have no effect on this. Since JS can't have blocking (continuously executing) code and realise on events and timers, you cannot have your game loop inside a while or for argument. Instead, you should use this to create your game loop to be called at given interval. There should only be one loop() function! Note that mouse mickeys (mouse_mx, etc.), and pressed indicators (pressed[] and mouse_pressed) will only work inside loop()
/// @param procedure function to be looped, preferably inline, but let's not talk coding styles here
/// @param speed speed in the same format as install_int_ex()
function loop(procedure,speed)
{
_loopproc = procedure;
var timer_id = window.setInterval(_uberloop,speed);
log("Game loop initialised!");
//_installed_timers.push({timer:procedure,id:timer_id});
}
/// time when ready() was called
var _loader_init_time;
/// Holds the download complete handler function
var _ready_proc;
/// Holds the download complete handler function
var _bar_proc;
/// checks if everything has downloaded in intervals
function _progress_check()
{
var num_assets = 0;
var num_loaded = 0;
for (var c=0;c<_downloadables.length;c++)
{
num_assets++;
if (_downloadables[c].type=="snd")
{
if (_downloadables[c].element.readyState>=_downloadables[c].element.HAVE_FUTURE_DATA) _downloadables[c].ready=true;
}
if (_downloadables[c].ready) num_loaded++;
}
if (_bar_proc) _bar_proc(num_assets/num_loaded);
if (num_loaded<num_assets)
{
window.setTimeout(_progress_check,100);
}
else
{
log("Loading complete! Took " + ((time()-_loader_init_time)/1000).toFixed(1) + " seconds!");
_ready_proc();
}
}
/// Default loading bar rendering
/// This function is used by ready() to display a simple loading bar on screen. You need to manually specify a dummy function if you don't want loading screen.
/// @param progress loading progress in 0.0 - 1.0 range
function loading_bar(progress)
{
rectfill(canvas,5,SCREEN_H-55,SCREEN_W-10,50,makecol(0,0,0));
rectfill(canvas,10,SCREEN_H-50,SCREEN_W-20,40,makecol(255,255,255));
rectfill(canvas,15,SCREEN_H-45,SCREEN_W-30,30,makecol(0,0,0));
rectfill(canvas,20,SCREEN_H-40,scaleclamp(progress,0,1,0,(SCREEN_W-40)),20,makecol(255,255,255));
}
/// Installs a handler to check if everything has downloaded.
/// You should always wrap your loop() function around it, unless there is nothing external you need. load_bitmap() and load_sample() all require some time to process and the execution cannot be stalled for that, so all code you wrap in this hander will only get executed after everything has loaded making sure you can access bitmap properties and data and play samples right away. Note that load_font() does not affect ready(), so you should always load your fonts first.
/// @param procedure function to be called when everything has loaded.
/// @param bar loading bar callback function, if omitted, equals to loading_bar() and renders a simple loading bar. it must accept one parameter, that is loading progress in 0.0-1.0 range.
function ready(procedure,bar)
{
_loader_init_time = time();
_ready_proc = procedure;
log("Loader initialised!");
if (bar) _bar_proc = bar; else _bar_proc = loading_bar;
window.setTimeout(_progress_check,100);
}
/// Removes interrupt
/// @param procedure interrupt procedure to be removed
function remove_int(procedure)
{
for(var c=0;c<_installed_timers.length;c++)
{
if (_installed_timers[c].timer == _installed_timers)
{
log("Removing interrupt " + _installed_timers[c].id + "!");
window.clearInterval(_installed_timers[c].id);
_installed_timers.splice(c,1);
return;
}
}
}
/// Removes all interrupts
function remove_all_ints()
{
for(var c=0;c<_installed_timers.length;c++)
{
window.clearInterval(_installed_timers[c].id);
}
_installed_timers = [];
log("Removed all interrupts!");
}
//@}
////////////////////////////////////////////
/// @name KEYBOARD ROUTINES
//@{
var KEY_A = 0x41, KEY_B = 0x42, KEY_C = 0x43, KEY_D = 0x44, KEY_E = 0x45, KEY_F = 0x46, KEY_G = 0x47, KEY_H = 0x48, KEY_I = 0x49, KEY_J = 0x4A, KEY_K = 0x4B, KEY_L = 0x4C, KEY_M = 0x4D, KEY_N = 0x4E, KEY_O = 0x4F, KEY_P = 0x50, KEY_Q = 0x51, KEY_R = 0x52, KEY_S = 0x53, KEY_T = 0x54, KEY_U = 0x55, KEY_V = 0x56, KEY_W = 0x57, KEY_X = 0x58, KEY_Y = 0x59, KEY_Z = 0x5A, KEY_0 = 0x30, KEY_1 = 0x31, KEY_2 = 0x32, KEY_3 = 0x33, KEY_4 = 0x34, KEY_5 = 0x35, KEY_6 = 0x36, KEY_7 = 0x37, KEY_8 = 0x38, KEY_9 = 0x39, KEY_0_PAD = 0x60, KEY_1_PAD = 0x61, KEY_2_PAD = 0x62, KEY_3_PAD = 0x63, KEY_4_PAD = 0x64, KEY_5_PAD = 0x65, KEY_6_PAD = 0x66, KEY_7_PAD = 0x67, KEY_8_PAD = 0x68, KEY_9_PAD = 0x69, KEY_F1 = 0x70, KEY_F2 = 0x71, KEY_F3 = 0x72, KEY_F4 = 0x73, KEY_F5 = 0x74, KEY_F6 = 0x75, KEY_F7 = 0x76, KEY_F8 = 0x77, KEY_F9 = 0x78, KEY_F10 = 0x79, KEY_F11 = 0x7a, KEY_F12 = 0x7b, KEY_ESC = 0x1B, KEY_TILDE = 0xc0, KEY_MINUS = 0xbd, KEY_EQUALS = 0xbb, KEY_BACKSPACE = 0x08, KEY_TAB = 0x09, KEY_OPENBRACE = 0xdb, KEY_CLOSEBRACE = 0xdd, KEY_ENTER = 0x0D, KEY_COLON = 0xba, KEY_QUOTE = 0xde, KEY_BACKSLASH = 0xdc, KEY_COMMA = 0xbc, KEY_STOP = 0xbe, KEY_SLASH = 0xBF, KEY_SPACE = 0x20, KEY_INSERT = 0x2D, KEY_DEL = 0x2E, KEY_HOME = 0x24, KEY_END = 0x23, KEY_PGUP = 0x21, KEY_PGDN = 0x22, KEY_LEFT = 0x25, KEY_RIGHT = 0x27, KEY_UP = 0x26, KEY_DOWN = 0x28, KEY_SLASH_PAD = 0x6F, KEY_ASTERISK = 0x6A, KEY_MINUS_PAD = 0x6D, KEY_PLUS_PAD = 0x6B, KEY_ENTER_PAD = 0x0D, KEY_PRTSCR = 0x2C, KEY_PAUSE = 0x13, KEY_EQUALS_PAD = 0x0C, KEY_LSHIFT = 0x10, KEY_RSHIFT = 0x10, KEY_LCONTROL = 0x11, KEY_RCONTROL = 0x11, KEY_ALT = 0x12, KEY_ALTGR = 0x12, KEY_LWIN = 0x5b, KEY_RWIN = 0x5c, KEY_MENU = 0x5d, KEY_SCRLOCK = 0x9d, KEY_NUMLOCK = 0x90, KEY_CAPSLOCK = 0x14;
/// Array of flags indicating state of each key.
/// Available keyboard scan codes are as follows:
/// * KEY_A ... KEY_Z,
/// * KEY_0 ... KEY_9,
/// * KEY_0_PAD ... KEY_9_PAD,
/// * KEY_F1 ... KEY_F12,
/// * KEY_ESC, KEY_TILDE, KEY_MINUS, KEY_EQUALS, KEY_BACKSPACE, KEY_TAB, KEY_OPENBRACE, KEY_CLOSEBRACE, KEY_ENTER, KEY_COLON, KEY_QUOTE, KEY_BACKSLASH, KEY_COMMA, KEY_STOP, KEY_SLASH, KEY_SPACE,
/// * KEY_INSERT, KEY_DEL, KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDN, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN,
/// * KEY_SLASH_PAD, KEY_ASTERISK, KEY_MINUS_PAD, KEY_PLUS_PAD, KEY_DEL_PAD, KEY_ENTER_PAD,
/// * KEY_PRTSCR, KEY_PAUSE,
/// * KEY_LSHIFT, KEY_RSHIFT, KEY_LCONTROL, KEY_RCONTROL, KEY_ALT, KEY_ALTGR, KEY_LWIN, KEY_RWIN, KEY_MENU, KEY_SCRLOCK, KEY_NUMLOCK, KEY_CAPSLOCK
/// * KEY_EQUALS_PAD, KEY_BACKQUOTE, KEY_SEMICOLON, KEY_COMMAND
var key = [];
/// Array of flags indicating in a key was just pressed since last loop()
/// Note that this will only work inside loop()
var pressed = [];
/// Array of flags indicating in a key was just released since last loop()
/// Note that this will only work inside loop()
var released = [];
/// Is keyboard even installed
var _keyboard_installed = false;
/// default keys to not suppress
var _default_enabled_keys = [KEY_F1,KEY_F2,KEY_F3,KEY_F4,KEY_F5,KEY_F6,KEY_F7,KEY_F8,KEY_F9,KEY_F10,KEY_F11,KEY_F12];
/// array of prevent default avoiders
var _enabled_keys = [];
/// Installs keyboard handlers
/// Unlike mouse, keyboard can be installed before initialising graphics, and the handlers will function over the entire website, as opposed to canvas only. After this call, the key[] array can be used to check state of each key. All keys will have their default action disabled, unless specified in the enable_keys array. This means that i.e. backspace won't go back, arrows won't scroll. By default, function keys (KEY_F1..KEY_F12) are the only ones not suppressed
/// @param enable_keys array of keys that are not going to have their default action prevented, i.e. [KEY_F5] will enable reloading the website. By default, if this is omitted, function keys are the only ones on the list.
function install_keyboard(enable_keys)
{
if (_keyboard_installed)
{
_allog("Keyboard already installed");
return -1;
}
if (enable_keys)
{
_enabled_keys = enable_keys;
} else {
_enabled_keys = _default_enabled_keys;
}
for (var c=0;c<0x80;c++)
{
key[c] = false;
pressed[c] = false;
released[c] = false;
}
document.addEventListener('keyup',_keyup);
document.addEventListener('keydown',_keydown);
_keyboard_installed = true;
log("Keyboard installed!");
return 0;
}
/// Uninstalls keyboard
function remove_keyboard()
{
if (!_keyboard_installed)
{
_allog("Keyboard not installed");
return -1;
}
document.removeEventListener('keyup',_keyup);
document.removeEventListener('keydown',_keydown);
_keyboard_installed = false;
log("Keyboard removed!");
return 0;
}
/// key down event handler
function _keydown(e)
{
if (!key[e.keyCode]) pressed[e.keyCode] = true;
key[e.keyCode] = true;
if (_enabled_keys.indexOf(e.keyCode)==-1) e.preventDefault();
}
/// key up event handler
function _keyup(e)
{
key[e.keyCode] = false;
released[e.keyCode] = true;
if (_enabled_keys.indexOf(e.keyCode)==-1) e.preventDefault();
}
//@}
////////////////////////////////////////////
// JOYSTICK ROUTINES
////////////////////////////////////////////
// SENSOR ROUTINES
////////////////////////////////////////////
/// @name BITMAP OBJECTS
//@{
/// Bitmap object, This is not a function.
/// This is not a function, it's the structure of bitmap object returned from load_bitmap() and create_bitmap(). For every bitmap laoded or created, a canvas element is created. Loaded images are then drawn onto the canvas, so that you can easily manipulate images and everything is consistent. You can also load a single file two times and modify it differently for each instance.
/// @param w bitmap width
/// @param h bitmap height
/// @param canvas underlying canvas element, used to draw the bitmap onto stuff
/// @param context canvas' rendering context, used to draw stuff onto this bitmap
/// @param ready flags whether loading of the bitmap is complete
/// @param type object type, "bmp" in this case
function BITMAP_OBJECT(w,h,canvas,context,ready,type) {}
/// Creates empty bitmap.
/// Creates a bitmap object of given dimensions and returns it.
/// @param width bitmap width
/// @param height bitmap height
/// @return bitmap object
function create_bitmap(width,height)
{
log("Creating bitmap at " + width + " x " + height + "!");
var cv = document.createElement('canvas');
cv.width = width;
cv.height = height;
var ctx = cv.getContext("2d");
return {w:width,h:height,canvas:cv,context:ctx,ready:true,type:"bmp"};
}
/// Loads bitmap from file
/// Loads image from file asynchronously. This means that the execution won't stall for the image, and it's data won't be accessible right off the start. You can check for bitmap object's 'ready' member to see if it has loaded, but you probably should avoid stalling execution for that, as JS doesn't really like that.
/// @param filename URL of image
/// @return bitmap object, or -1 on error
function load_bitmap(filename)
{
log("Loading bitmap " + filename + "...");
var img = new Image();
img.src = filename;
var now = time();
var cv = document.createElement('canvas');
var ctx = cv.getContext("2d");
var bmp = {canvas:cv,context:ctx,w:-1,h:-1,ready:false,type:"bmp"};
_downloadables.push(bmp);
img.onload = function(){
log("Bitmap " + filename + " loaded, size: " + img.width + " x " + img.height + "!");
bmp.canvas.width = img.width;
bmp.canvas.height = img.height;
bmp.context.drawImage(img,0,0);
bmp.w = img.width;
bmp.h = img.height;
bmp.ready=true;
};
return bmp;
}
/// Wrapper for load_bitmap
function load_bmp(filename)
{
return load_bitmap(filename);
}
/// Loads sprite sheet
/// Loads image file containing animation frames, slices it up and returns array of frame bitmaps.
/// @param filename URL of image
/// @param w,h frame dimensions
/// @return bitmap object, or -1 on error
function load_sheet(filename,w,h)
{
log("Loading spritesheet " + filename + "...");
var img = new Image();
img.src = filename;
var now = time();
var cv = document.createElement('canvas');
var ctx = cv.getContext("2d");
var bmp = {canvas:cv,context:ctx,w:-1,h:-1,ready:false,type:"bmp"};
var sheet = [];
_downloadables.push(bmp);
img.onload = function(){
log("Sheet " + filename + " loaded, size: " + img.width + " x " + img.height + "!");
bmp.canvas.width = img.width;
bmp.canvas.height = img.height;
bmp.context.drawImage(img,0,0);
bmp.w = img.width;
bmp.h = img.height;
bmp.ready=true;
var nx=Math.floor(bmp.w/w),ny=Math.floor(bmp.h/h);
for(var y=0;y<ny;y++)
{
for (var x=0;x<nx;x++)
{
var frame = create_bitmap(w,h);
blit(bmp,frame,x*w,y*h,0,0,w,h);
sheet.push(frame);
}
}
log("Created " + frame.length + " frames, each is " + w + "x" + h + "!");
};
return sheet;
}
//@}
////////////////////////////////////////////
/// @name GRAPHICS MODES
//@{
/// Screen bitmap
/// This is the bitmap object representing the main drawing canvas. Drawing anything on the screen bitmap displays it.
var canvas;
var _gfx_installed = false;
/// Screen bitmap width in pixels
var SCREEN_W = 0;
/// Screen bitmap height in pixels
var SCREEN_H = 0;
/// default font
var font;
/// Enables graphics.
/// This function should be before calling any other graphics routines. It selects the canvas element for rendering and sets the resolution. It also loads the default font.
/// @param canvas_id id attribute of canvas to be used for drawing.
/// @param width canvas width in pixels, 0 for don't care (will use actual canvas size)
/// @param height canvas height in pixels, 0 for don't care (will use actual canvas size)
/// @param smooth disable/enable pixel smoothing, deaults to true
/// @return 0 on success or -1 on error
function set_gfx_mode(canvas_id,width,height,smooth)
{
smooth = typeof smooth !== 'undefined' ? smooth : true;
var cv = document.getElementById(canvas_id);
if (!cv)
{
_error("Can't find canvas with id " + canvas_id);
return -1;
}
var rect = cv.getBoundingClientRect();
if (!width) width=rect.width;
if (!height) height=rect.height;
cv.width = width;
cv.height = height;
var ctx = cv.getContext("2d");
// turn off image aliasing
ctx.mozImageSmoothingEnabled = smooth;
ctx.webkitImageSmoothingEnabled = smooth;
ctx.imageSmoothingEnabled = smooth;
SCREEN_W = width;
SCREEN_H = height;
canvas = {w:width,h:height,canvas:cv,context:ctx,ready:true};
font = load_base64_font(_cartoon_woff);
_gfx_installed = true;
log("Graphics mode set to " + width + " x " + height);
return 0;
}
//@}
////////////////////////////////////////////
/// @name DRAWING PRIMITIVES
// @{
/// Pi
var PI = 3.14159265;
/// Pi * 2
var PI2 = 2*3.14159265;
/// Pi / 2
var PI_2 = 3.14159265/2;
/// Pi / 3
var PI_3 = 3.14159265/3;
/// Pi / 4
var PI_4 = 3.14159265/4;
/// Converts degrees to radians.
/// Also, changes clockwise to anticlockwise.
/// @param d value in degrees to be converted
/// @return -d*PI/180.0f
function RAD(d) { return -d*PI/180.0;}
/// Converts radians to degrees.
/// Also, changes anticlockwise to clockwise.
/// @param r value in radians to be converted
/// @return -r*180.0f/PI
function DEG(r) { return -r*180.0/PI;}
/// Helper for setting fill style
function _fillstyle(bitmap,colour)
{
bitmap.context.fillStyle = 'rgba('+ getr(colour) + ',' + getg(colour) + ',' + getb(colour) + ',' + getaf(colour) + ')';
}
/// Helper for setting stroke style
function _strokestyle(bitmap,colour,width)
{
if (!width) width=1;
bitmap.context.lineWidth = width;
bitmap.context.strokeStyle = 'rgba('+ getr(colour) + ',' + getg(colour) + ',' + getb(colour) + ',' + getaf(colour) + ')';
}
/// Creates a 0xAARRGGBB from values
/// Overdrive is not permitted, so values over 255 (0xff) will get clipped.
/// @param r red component in 0-255 range
/// @param g green component in 0-255 range
/// @param b blue component in 0-255 range
/// @param a alpha component in 0-255 range, defaults to 255 (fully opaque)
/// @return colour in 0xAARRGGBB format
function makecol(r,g,b,a)
{
a = typeof a !== 'undefined' ? a : 255;
return (a<<24)|((r&0xff)<<16)|((g&0xff)<<8)|((b&0xff));
}
/// Creates 0xAARRGGBB from values
/// This is a float version of makecol, where all components should be in 0.0-1.0 range.
/// @param r red component in 0.0-1.0 range
/// @param g green component in 0.0-1.0 range
/// @param b blue component in 0.0-1.0 range
/// @param a alpha component in 0.0-1.0 range, defaults to 1.0 (fully opaque)
/// @return colour in 0xAARRGGBB format
function makecolf(r,g,b,a)
{
a = typeof a !== 'undefined' ? a : 1.0;
return makecol(r*255,g*255,b*255,a*255);
}
/// Gets red bits from 0xRRGGBB
/// This one does clip.
/// @param colour colour in 0xAARRGGBB format
/// @return red component in 0-255 range
function getr(colour)
{
return (colour>>16)&0xff;
}
/// Gets red bits from 0xRRGGBB
/// This one too.
/// @param colour colour in 0xAARRGGBB format
/// @return green component in 0-255 range
function getg(colour)
{
return (colour>>8)&0xff;
}
/// Gets red bits from 0xRRGGBB
/// This one clips as well.
/// @param colour colour in 0xAARRGGBB format
/// @return blue component in 0-255 range
function getb(colour)
{
return colour&0xff;
}
/// Gets alpha bits from 0xAARRGGBB
/// This one doesn't.
/// @param colour colour in 0xAARRGGBB format
/// @return alpha component in 0-255 range
function geta(colour)
{
return colour>>>24;
}
/// Float (0.0-1.0) version of getr()
/// @param colour colour in 0xAARRGGBB format
/// @return red component in 0.0-1.0 range
function getrf(colour)
{
return (colour>>16)&0xff/255.0;
}
/// Float (0.0-1.0) version of getg()
/// @param colour colour in 0xAARRGGBB format
/// @return green component in 0.0-1.0 range
function getgf(colour)
{
return (colour>>8)&0xff/255.0;
}
/// Float (0.0-1.0) version of getb()
/// @param colour colour in 0xAARRGGBB format
/// @return blue component in 0.0-1.0 range
function getbf(colour)
{
return colour&0xff/255.0;
}
/// Float (0.0-1.0) version of geta()
/// @param colour colour in 0xAARRGGBB format
/// @return alpha component in 0.0-1.0 range
function getaf(colour)
{
return (colour>>>24)/255.0;
}
/// Gets pixel colour from bitmap
/// Reads pixel from bitmap at given coordinates. This is probably super slow, and shouldn't be used inside loops.
/// @param bitmap bitmap object to poll
/// @param x x coordinate of pixel
/// @param y y coordinate of pixel
/// @return colour in 0xAARRGGBB format
function getpixel(bitmap,x,y)
{
var data = bitmap.context.getImageData(x,y,1,1).data;
return (data[3]<<24)|((data[0]&0xff)<<16)|((data[1]&0xff)<<8)|((data[2]&0xff));
}