shmup.html
1	<html><head>
2 <title>PICO-8 Cartridge</title>
3 <meta name="viewport" content="width=device-width, user-scalable=no">
4 <script type="text/javascript">
5
6 // Default shell for PICO-8 0.2.2 (includes @weeble's gamepad mod 1.0)
7 // This file is available under a CC0 license https://creativecommons.org/share-your-work/public-domain/cc0/
8 // (note: "this file" does not include any cartridge or cartridge artwork injected into a derivative html file when using the PICO-8 html exporter)
9
10 // options
11
12 // fullscreen, sound, close button at top when playing on touchscreen
13 var p8_allow_mobile_menu = true;
14
15 // p8_autoplay true to boot the cartridge automatically after page load when possible
16 // if the browser can not create an audio context outside of a user gesture (e.g. on iOS), p8_autoplay has no effect
17 var p8_autoplay = false;
18
19 // When pico8_state is defined, PICO-8 will set .is_paused, .sound_volume and .frame_number each frame
20 // (used for determining button icons)
21 var pico8_state = [];
22
23 // When pico8_buttons is defined, PICO-8 reads each int as a bitfield holding that player's button states
24 // 0x1 left, 0x2 right, 0x4 up, 0x8 right, 0x10 O, 0x20 X, 0x40 menu
25 // (used by p8_update_gamepads)
26 var pico8_buttons = [0, 0, 0, 0, 0, 0, 0, 0]; // max 8 players
27
28 // When pico8_mouse is defined, PICO-8 reads the 3 integers as X, Y and a bitfield for buttons: 0x1 LMB, 0x2 RMB
29 var pico8_mouse = [];
30
31 // used to display number of detected joysticks
32 var pico8_gamepads = {};
33 pico8_gamepads.count = 0;
34
35 // When pico8_gpio is defined, reading and writing to gpio pins will read and write to these values
36 var pico8_gpio = new Array(128);
37
38 // When pico8_audio_context context is defined, the html shell (this file) is responsible for creating and managing it.
39 // This makes satisfying browser requirements easier -- e.g. initialising audio from a short script in response to a user action.
40 // Otherwise PICO-8 will try to create and use its own context.
41
42 var pico8_audio_context;
43
44
45 // menu button and controller graphics
46 p8_gfx_dat={
47 "p8b_pause1": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAOUlEQVRIx2NgGPbg/8cX/0F46FtAM4vobgHVLRowC6hm0YBbQLFFoxaM4FQ0dHPy0C1Nh26NNugBAAnizNiMfvbGAAAAAElFTkSuQmCC",
48 "p8b_controls":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAQ0lEQVRIx2NgGAXEgP8fX/ynBaap4XBLhqcF1IyfYWQBrZLz0LEAlzqqxQFVLcAmT3MLqJqTaW7B4CqLaF4fjIIBBwBL/B2vqtPVIwAAAABJRU5ErkJggg==",
49 "p8b_full":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAN0lEQVRIx2NgGPLg/8cX/2mJ6WcBrUJm4CwgOSgGrQVEB8WoBaMWDGMLhm5OHnql6dCt0YY8AAA9oZm+9Z9xQAAAAABJRU5ErkJggg==",
50 "p8b_pause0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAKUlEQVRIx2NgGHbg/8cX/7FhctWNWjBqwagFoxaMWjBqwagF5Fkw5AAAPaGZvsIUtXUAAAAASUVORK5CYII=",
51 "p8b_sound0":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAANklEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwavBcRG4qgFoxYMZwuGfk4efqXp8KnRBj0AAMz7cLDnG4FeAAAAAElFTkSuQmCC",
52 "p8b_sound1":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAPUlEQVRIx2NgGDHg/8cX/5Hx0LEA3cChYwEugwhZQLQDqG4BsZFIKMhGLRi1YChbMPRz8vArTYdPjTboAQCSVgpXUWQAMAAAAABJRU5ErkJggg==",
53 "p8b_close":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAU0lEQVRIx2NkoDFgpJsF/z+++I8iwS9BkuW49A+cBcRaREgf/Swg1SJi1dHfAkIG4EyOOIJy4Cwg1iJCiWDUAvItGLqpaOjm5KFfmg79Gm3ItioAl+mAGVYIZUUAAAAASUVORK5CYII=",
54
55 "controls_left_panel":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAEI0lEQVR42u3dMU7DQBCG0Tjam9DTcP8jpEmfswS5iHBhAsLxev/hvQY6pGXyZRTQ+nQCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHqbHEEtl+vt7hS+fLy/mXHBQqxEi/6aI/AiFW9SnB2BWDkDBAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwONJ8tYvrXRAsImK19j0IFsPGSrQQLCJiNV+et7xAT7QQLIaN1dr3ooVgMWysRAvBIipWooVgERUr0UKwiIqVaCFYRMVKtBAsomIlWggWUbESLQSLqFiJFoJFVKxEC8EiKlaihWARFSvRQrDYJSSVfhaCBSBYAIIFCBbAHpoj4Bl/scOGBWDD4lX8iwE2LADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAECxAsAMECECxAsMh1ud7uTsHZVDcZyFo8Yt5sVJ6NyUAaSNEyIymaXwZepIKd4mwoQbAFC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQI4UHME2/10QZq7usyBObBhRQwpmBUb1nADuPbuaUD/p2ezMH+1admwhosVfBcxb2SCJVaIlmAhVoiWYIkVoiVagiVWiJZgiZVYIVqCJVaIlmgJllghWoIlViBagiVWiJZoCZZYIVqCJVYgWoIlViBaggUIlnc0sPELlmghVmIlWKKFWAmWaIFYCZZoIVYIlmghVoIlWiBWgiVaiJVgIVqIlWCJFoiVYIkWYiVYiBZiJViihViJ1XbNEWyL1mMQRYvfvIGJlQ1rmE0LzIoNyyBiDrBhAYIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgDc+Nn1D/tdH8YupwgZy5qG4ykKIlVmZDsDjshSlazqQqH7p793Q2CBaAYAGCBSBYAIIFCBaAYAEIFiBYAIIFIFiAYAEIFoBgAYIFIFgAggUIFoBgAQgWIFgAggUgWIBgAQgWwENzBKxZPub9CJ7WjA0LsGFRV+9N5+jNDhsWgGABggUgWACCxW56fgjuA3cEiz9Z/nWwR0iWP8P/YCFYDBstsUKwiIiWWCFYRERLrBAsIqIlVggWEdESKwSLiGiJFYJFRLTECsEiIlpihWARES2xQrCIiJZYIVhEREusECwioiVWCBYx0RIrBIuoaIkVr+YhFHTZtMCGBQgWgGABCBYgWACCBSBYgGABCBaAYAGCBSBYAIIFCBbj2uOR8s6AEbhexgsWYri3SKhKczcXAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMA2n+e0UMDzh3yTAAAAAElFTkSuQmCC",
56
57
58 "controls_right_panel":"data:image/png;base64,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",
59
60 };
61
62
63 // added 0.2.1: work-around for iOS/Safari running from an iFrame (e.g. from itch.io page):
64 // touch events only register after adding dummy listeners on document.
65
66 document.addEventListener('touchstart', {});
67 document.addEventListener('touchmove', {});
68 document.addEventListener('touchend', {});
69
70
71 // --------------------------------------------------------------------------------------------------------------------------------
72 // pico-8 0.2.2: allow dropping files
73 var p8_dropped_cart = null;
74 var p8_dropped_cart_name = "";
75 function p8_drop_file(e)
76 {
77 // console.log("@@ dropping file...");
78
79 e.stopPropagation();
80 e.preventDefault();
81
82 if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0])
83 {
84 // read from file
85 reader = new FileReader();
86 reader.onload = function (event) {
87 p8_dropped_cart_name = e.dataTransfer.files[0].name;
88 p8_dropped_cart = reader.result;
89 // data:image/png;base64
90 e.stopPropagation();
91 e.preventDefault();
92 };
93 reader.readAsDataURL(e.dataTransfer.files[0]);
94 codo_command = 9; // read directly from p8_dropped_cart with libb64 decoder
95 }
96 else
97 {
98 // read from url (or data url)
99 txt = e.dataTransfer.getData('Text');
100 if (txt){
101 p8_dropped_cart_name = "untitled.p8.png";
102 p8_dropped_cart = txt;
103 codo_command = 9;
104 }
105 }
106 }
107 function nop(evt) {
108 evt.stopPropagation();
109 evt.preventDefault();
110 }
111 function dragover(evt) {
112 evt.stopPropagation();
113 evt.preventDefault();
114 Module.pico8DragOver();
115 }
116 function dragstop(evt) {
117 evt.stopPropagation();
118 evt.preventDefault();
119 Module.pico8DragStop();
120 }
121 // --------------------------------------------------------------------------------------------------------------------------------
122
123
124 var p8_buttons_hash = -1;
125 function p8_update_button_icons()
126 {
127 // buttons only appear when running
128 if (!p8_is_running)
129 {
130 requestAnimationFrame(p8_update_button_icons);
131 return;
132 }
133 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
134
135 // hash based on: pico8_state.sound_volume pico8_state.is_paused bottom_margin left is_fullscreen p8_touch_detected
136 var hash = 0;
137 hash = pico8_state.sound_volume;
138 if (pico8_state.is_paused) hash += 0x100;
139 if (p8_touch_detected) hash += 0x200;
140 if (is_fullscreen) hash += 0x400;
141
142 if (p8_buttons_hash == hash)
143 {
144 requestAnimationFrame(p8_update_button_icons);
145 return;
146 }
147
148 p8_buttons_hash = hash;
149 // console.log("@@ updating button icons");
150
151 els = document.getElementsByClassName('p8_menu_button');
152 for (i = 0; i < els.length; i++)
153 {
154 el = els[i];
155 index = el.id;
156 if (index == 'p8b_sound') index += (pico8_state.sound_volume == 0 ? "0" : "1"); // 1 if undefined
157 if (index == 'p8b_pause') index += (pico8_state.is_paused > 0 ? "1" : "0"); // 0 if undefined
158
159 new_str = '<img width=24 height=24 style="pointer-events:none" src="'+p8_gfx_dat[index]+'">';
160 if (el.innerHTML != new_str)
161 el.innerHTML = new_str;
162
163
164
165
166 // hide all buttons for touch mode (can pause with menu buttons)
167
168 var is_visible = p8_is_running;
169
170 if ((!p8_touch_detected || !p8_allow_mobile_menu) && el.parentElement.id == "p8_menu_buttons_touch")
171 is_visible = false;
172
173 if (p8_touch_detected && el.parentElement.id == "p8_menu_buttons")
174 is_visible = false;
175
176 if (is_fullscreen)
177 is_visible = false;
178
179 if (is_visible)
180 el.style.display="";
181 else
182 el.style.display="none";
183 }
184 requestAnimationFrame(p8_update_button_icons);
185 }
186
187
188
189 function abs(x)
190 {
191 return x < 0 ? -x : x;
192 }
193
194 // step 0 down 1 drag 2 up (not used)
195 function pico8_buttons_event(e, step)
196 {
197 if (!p8_is_running) return;
198
199 pico8_buttons[0] = 0;
200
201 if (step == 2 && typeof(pico8_mouse) !== 'undefined')
202 {
203 pico8_mouse[2] = 0;
204 }
205
206 var num = 0;
207 if (e.touches) num = e.touches.length;
208
209 if (num == 0 && typeof(pico8_mouse) !== 'undefined')
210 {
211 // no active touches: release mouse button from anywhere on page. (maybe redundant? but just in case)
212 pico8_mouse[2] = 0;
213 }
214
215
216 for (var i = 0; i < num; i++)
217 {
218 var touch = e.touches[i];
219 var x = touch.clientX;
220 var y = touch.clientY;
221 var w = window.innerWidth;
222 var h = window.innerHeight;
223
224 var r = Math.min(w,h) / 12;
225 if (r > 40) r = 40;
226
227 // mouse (0.1.12d)
228
229 let canvas = document.getElementById("canvas");
230 if (p8_touch_detected)
231 if (typeof(pico8_mouse) !== 'undefined')
232 if (canvas)
233 {
234 var rect = canvas.getBoundingClientRect();
235 //console.log(rect.top, rect.right, rect.bottom, rect.left, x, y);
236
237 if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom)
238 {
239 pico8_mouse = [
240 Math.floor((x - rect.left) * 128 / (rect.right - rect.left)),
241 Math.floor((y - rect.top) * 128 / (rect.bottom - rect.top)),
242 step < 2 ? 1 : 0
243 ];
244 // return; // commented -- blocks overlapping buttons
245 }else
246 {
247 pico8_mouse[2] = 0;
248 }
249 }
250
251
252 // buttons
253
254 b = 0;
255
256 if (y < h - r*8)
257 {
258 // no controller buttons up here; includes canvas and menu buttons at top in touch mode
259 }
260 else
261 {
262 e.preventDefault();
263
264 if ((y < h - r*6) && y > (h - r*8))
265 {
266 // menu button: half as high as X O button
267 // stretch across right-hand half above X O buttons
268 if (x > w - r*3)
269 b |= 0x40;
270 }
271 else if (x < w/2 && x < r*6)
272 {
273 // stick
274
275 mask = 0xf; // dpad
276 var cx = 0 + r*3;
277 var cy = h - r*3;
278
279 deadzone = r/3;
280 var dx = x - cx;
281 var dy = y - cy;
282
283 if (abs(dx) > abs(dy) * 0.6) // horizontal
284 {
285 if (dx < -deadzone) b |= 0x1;
286 if (dx > deadzone) b |= 0x2;
287 }
288 if (abs(dy) > abs(dx) * 0.6) // vertical
289 {
290 if (dy < -deadzone) b |= 0x4;
291 if (dy > deadzone) b |= 0x8;
292 }
293 }
294 else if (x > w - r*6)
295 {
296 // button; diagonal split from bottom right corner
297
298 mask = 0x30;
299
300 // one or both of [X], [O]
301 if ( (h-y) > (w-x) * 0.8) b |= 0x10;
302 if ( (w-x) > (h-y) * 0.8) b |= 0x20;
303 }
304 }
305
306 pico8_buttons[0] |= b;
307
308 }
309 }
310
311 // p8_update_layout_hash is used to decide when to update layout (expensive especially when part of a heavy page)
312 var p8_update_layout_hash = -1;
313 var last_windowed_container_height = 512;
314 var p8_layout_frames = 0;
315
316 function p8_update_layout()
317 {
318 var canvas = document.getElementById("canvas");
319 var p8_playarea = document.getElementById("p8_playarea");
320 var p8_container = document.getElementById("p8_container");
321 var p8_frame = document.getElementById("p8_frame");
322 var csize = 512;
323 var margin_top = 0;
324 var margin_left = 0;
325
326 // page didn't load yet? first call should be after p8_frame is created so that layout doesn't jump around.
327 if (!canvas || !p8_playarea || !p8_container || !p8_frame)
328 {
329 p8_update_layout_hash = -1;
330 requestAnimationFrame(p8_update_layout);
331 return;
332 }
333
334 p8_layout_frames ++;
335
336 // assumes frame doesn't have padding
337
338 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
339 var frame_width = p8_frame.offsetWidth;
340 var frame_height = p8_frame.offsetHeight;
341
342 if (is_fullscreen)
343 {
344 // same as window
345 frame_width = window.innerWidth;
346 frame_height = window.innerHeight;
347 }
348 else{
349 // never larger than window // (happens when address bar is down in portraight mode on phone)
350 frame_width = Math.min(frame_width, window.innerWidth);
351 frame_height = Math.min(frame_height, window.innerHeight);
352 }
353
354 // as big as will fit in a frame..
355 csize = Math.min(frame_width,frame_height);
356
357 // .. but never more than 2/3 of longest side for touch (e.g. leave space for controls on iPad)
358 if (p8_touch_detected && p8_is_running)
359 {
360 var longest_side = Math.max(window.innerWidth,window.innerHeight);
361 csize = Math.min(csize, longest_side * 2/3);
362 }
363
364 // pixel perfect: quantize to closest multiple of 128
365 // only when large display (desktop)
366 if (frame_width >= 512 && frame_height >= 512)
367 {
368 csize = (csize+1) & ~0x7f;
369 }
370
371 // csize should never be higher than parent frame
372 // (otherwise stretched large when fullscreen and then return)
373 if (!is_fullscreen && p8_frame)
374 csize = Math.min(csize, last_windowed_container_height); // p8_frame_0 parent
375
376
377 if (is_fullscreen)
378 {
379 // always center horizontally
380 margin_left = (frame_width - csize)/2;
381
382 if (p8_touch_detected)
383 {
384 if (window.innerWidth < window.innerHeight)
385 {
386 // portrait: keep at y=40 (avoid rounded top corners / camera nub thing etc.)
387 margin_top = Math.min(40, frame_height - csize);
388 }
389 else
390 {
391 // landscape: put a little above vertical center
392 margin_top = (frame_height - csize)/4;
393 }
394 }
395 else{
396 // non-touch: center vertically
397 margin_top = (frame_height - csize)/2;
398 }
399 }
400
401 // skip if relevant state has not changed
402
403 var update_hash = csize + margin_top * 1000.3 + margin_left * 0.001 + frame_width * 333.33 + frame_height * 772.15134;
404 if (is_fullscreen) update_hash += 0.1237;
405
406 // unexpected things can happen in the first few seconds, so just keep re-calculating layout. wasm version breaks layout otherwise.
407 // also: bonus refresh at 5, 8 seconds just in case ._.
408 if (p8_layout_frames < 180 || p8_layout_frames == 60*5 || p8_layout_frames == 60*8 )
409 update_hash = p8_layout_frames;
410
411 if (!is_fullscreen) // fullscreen: update every frame for safety. should be cheap!
412 if (!p8_touch_detected) // mobile: update every frame because nothing can be trusted
413 if (p8_update_layout_hash == update_hash)
414 {
415 //console.log("p8_update_layout(): skipping");
416 requestAnimationFrame(p8_update_layout);
417 return;
418 }
419 p8_update_layout_hash = update_hash;
420
421 // record this for returning to original size after fullscreen pushes out container height (argh)
422 if (!is_fullscreen && p8_frame)
423 last_windowed_container_height = p8_frame.parentNode.parentNode.offsetHeight;
424
425
426 // mobile in portrait mode: put screen at top (w / a little extra space for fullscreen button if needed)
427 // (don't cart too about buttons overlapping screen)
428 if (p8_touch_detected && p8_is_running && document.body.clientWidth < document.body.clientHeight)
429 p8_playarea.style.marginTop = p8_allow_mobile_menu ? 32 : 8;
430 else if (p8_touch_detected && p8_is_running) // landscape: slightly above vertical center (only relevant for iPad / highres devices)
431 p8_playarea.style.marginTop = (document.body.clientHeight - csize) / 4;
432 else
433 p8_playarea.style.marginTop = "";
434
435 canvas.style.width = csize;
436 canvas.style.height = csize;
437
438 // to do: this should just happen from css layout
439 canvas.style.marginLeft = margin_left;
440 canvas.style.marginTop = margin_top;
441
442 p8_container.style.width = csize;
443 p8_container.style.height = csize;
444
445 // set menu buttons position to bottom right
446 el = document.getElementById("p8_menu_buttons");
447 el.style.marginTop = csize - el.offsetHeight;
448
449 if (p8_touch_detected && p8_is_running)
450 {
451 // turn off pointer events to prevent double-tap zoom etc (works on Android)
452 // don't want this for desktop because breaks mouse input & click-to-focus when using codo_textarea
453 canvas.style.pointerEvents = "none";
454
455 p8_container.style.marginTop = "0px";
456
457 // buttons
458
459 // same as touch event handling
460 var w = window.innerWidth;
461 var h = window.innerHeight;
462 var r = Math.min(w,h) / 12;
463
464 if (r > 40) r = 40;
465
466 el = document.getElementById("controls_right_panel");
467 el.style.left = w-r*6;
468 el.style.top = h-r*7;
469 el.style.width = r*6;
470 el.style.height = r*7;
471 if (el.getAttribute("src") != p8_gfx_dat["controls_right_panel"]) // optimisation: avoid reload? (browser should handle though)
472 el.setAttribute("src", p8_gfx_dat["controls_right_panel"]);
473
474 el = document.getElementById("controls_left_panel");
475 el.style.left = 0;
476 el.style.top = h-r*6;
477 el.style.width = r*6;
478 el.style.height = r*6;
479 if (el.getAttribute("src") != p8_gfx_dat["controls_left_panel"]) // optimisation: avoid reload? (browser should handle though)
480 el.setAttribute("src", p8_gfx_dat["controls_left_panel"]);
481
482 // scroll to cart (commented; was a failed attempt to prevent scroll-on-drag on some browsers)
483 // p8_frame.scrollIntoView(true);
484
485 document.getElementById("touch_controls_gfx").style.display="table";
486 document.getElementById("touch_controls_background").style.display="table";
487
488 }
489 else{
490 document.getElementById("touch_controls_gfx").style.display="none";
491 document.getElementById("touch_controls_background").style.display="none";
492 }
493
494 if (!p8_is_running)
495 {
496 p8_playarea.style.display="none";
497 p8_container.style.display="flex";
498 p8_container.style.marginTop="auto";
499
500 el = document.getElementById("p8_start_button");
501 if (el) el.style.display="flex";
502 }
503 requestAnimationFrame(p8_update_layout);
504 }
505
506
507 var p8_touch_detected = false;
508 addEventListener("touchstart", function(event)
509 {
510 p8_touch_detected = true;
511
512 // hide codo_textarea -- clipboard support on mobile is not feasible
513 el = document.getElementById("codo_textarea");
514 if (el && el.style.display != "none"){
515 el.style.display="none";
516 }
517
518 }, {passive: true});
519
520 function p8_create_audio_context()
521 {
522 if (pico8_audio_context)
523 {
524 try {
525 pico8_audio_context.resume();
526 }
527 catch(err) {
528 console.log("** pico8_audio_context.resume() failed");
529 }
530 return;
531 }
532
533 var webAudioAPI = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext;
534 if (webAudioAPI)
535 {
536 pico8_audio_context = new webAudioAPI;
537
538 // wake up iOS
539 if (pico8_audio_context)
540 {
541 try {
542 var dummy_source_sfx = pico8_audio_context.createBufferSource();
543 dummy_source_sfx.buffer = pico8_audio_context.createBuffer(1, 1, 22050); // dummy
544 dummy_source_sfx.connect(pico8_audio_context.destination);
545 dummy_source_sfx.start(1, 0.25); // gives InvalidStateError -- why? hasn't been played before
546 //dummy_source_sfx.noteOn(0); // deleteme
547 }
548 catch(err) {
549 console.log("** dummy_source_sfx.start(1, 0.25) failed");
550 }
551 }
552 }
553 }
554
555 function p8_close_cart()
556 {
557 // just reload page! used for touch buttons -- hard to roll back state
558 window.location.hash = ""; // triggers reload
559 }
560
561 var p8_is_running = false;
562 var p8_script = null;
563 var Module = null;
564 function p8_run_cart()
565 {
566 if (p8_is_running) return;
567 p8_is_running = true;
568
569 // touch: hide everything except p8_frame_0
570 if (p8_touch_detected)
571 {
572 el = document.getElementById("body_0");
573 el2 = document.getElementById("p8_frame_0");
574 if (el && el2)
575 {
576 el.style.display="none";
577 el.parentNode.appendChild(el2);
578 }
579 }
580
581 // create audio context and wake it up (for iOS -- needs happen inside touch event)
582 p8_create_audio_context();
583
584 // show touch elements
585 els = document.getElementsByClassName('p8_controller_area');
586 for (i = 0; i < els.length; i++)
587 els[i].style.display="";
588
589
590 // install touch events. These also serve to block scrolling / pinching / zooming on phones when p8_is_running
591 // moved event.preventDefault(); calls into pico8_buttons_event() (want to let top buttons pass through)
592 addEventListener("touchstart", function(event){ pico8_buttons_event(event, 0); }, {passive: false});
593 addEventListener("touchmove", function(event){ pico8_buttons_event(event, 1); }, {passive: false});
594 addEventListener("touchend", function(event){ pico8_buttons_event(event, 2); }, {passive: false});
595
596
597 // load and run script
598 e = document.createElement("script");
599 p8_script = e;
600 e.onload = function(){
601
602 // show canvas / menu buttons only after loading
603 el = document.getElementById("p8_playarea");
604 if (el) el.style.display="table";
605
606 if (typeof(p8_update_layout_hash) !== 'undefined')
607 p8_update_layout_hash = -77;
608 if (typeof(p8_buttons_hash) !== 'undefined')
609 p8_buttons_hash = -33;
610
611
612 }
613 e.type = "application/javascript";
614 e.src = "shmup.js";
615 e.id = "e_script";
616
617 document.body.appendChild(e); // load and run
618
619 // hide start button and show canvas / menu buttons. hide start button
620 el = document.getElementById("p8_start_button");
621 if (el) el.style.display="none";
622
623 // add #playing for touchscreen devices (allows back button to close)
624 // X button can also be used to trigger this
625 if (p8_touch_detected)
626 {
627 window.location.hash = "#playing";
628 window.onhashchange = function()
629 {
630 if (window.location.hash.search("playing") < 0)
631 window.location.reload();
632 }
633 }
634
635 // install drag&drop listeners
636 {
637 let canvas = p8_document().getElementById("canvas");
638 if (canvas)
639 {
640 canvas.addEventListener('dragenter', dragover, false);
641 canvas.addEventListener('dragover', dragover, false);
642 canvas.addEventListener('dragleave', dragstop, false);
643 canvas.addEventListener('drop', nop, false);
644 canvas.addEventListener('drop', p8_drop_file, false);
645 }
646 }
647 }
648
649
650 // Gamepad code
651
652 var P8_BUTTON_O = {action:'button', code: 0x10};
653 var P8_BUTTON_X = {action:'button', code: 0x20};
654 var P8_DPAD_LEFT = {action:'button', code: 0x1};
655 var P8_DPAD_RIGHT = {action:'button', code: 0x2};
656 var P8_DPAD_UP = {action:'button', code: 0x4};
657 var P8_DPAD_DOWN = {action:'button', code: 0x8};
658 var P8_MENU = {action:'menu'};
659 var P8_NO_ACTION = {action:'none'};
660
661 var P8_BUTTON_MAPPING = [
662 // ref: https://w3c.github.io/gamepad/#remapping
663 P8_BUTTON_O, // Bottom face button
664 P8_BUTTON_X, // Right face button
665 P8_BUTTON_X, // Left face button
666 P8_BUTTON_O, // Top face button
667 P8_NO_ACTION, // Near left shoulder button (L1)
668 P8_NO_ACTION, // Near right shoulder button (R1)
669 P8_NO_ACTION, // Far left shoulder button (L2)
670 P8_NO_ACTION, // Far right shoulder button (R2)
671 P8_MENU, // Left auxiliary button (select)
672 P8_MENU, // Right auxiliary button (start)
673 P8_NO_ACTION, // Left stick button
674 P8_NO_ACTION, // Right stick button
675 P8_DPAD_UP, // Dpad up
676 P8_DPAD_DOWN, // Dpad down
677 P8_DPAD_LEFT, // Dpad left
678 P8_DPAD_RIGHT, // Dpad right
679 ];
680
681 // Track which player is controller by each gamepad. Gamepad index i controls the
682 // player with index pico8_gamepads_mapping[i]. Gamepads with null player are
683 // currently unassigned - they get assigned to a player when a button is pressed.
684 var pico8_gamepads_mapping = [];
685
686 function p8_unassign_gamepad(gamepad_index) {
687 if (pico8_gamepads_mapping[gamepad_index] == null) {
688 return;
689 }
690 pico8_buttons[pico8_gamepads_mapping[gamepad_index]] = 0;
691 pico8_gamepads_mapping[gamepad_index] = null;
692 }
693
694
695 function p8_first_player_without_gamepad(max_players) {
696 var allocated_players = pico8_gamepads_mapping.filter(function(x) { return x != null; });
697 var sorted_players = Array.from(allocated_players).sort();
698 for (var desired = 0; desired < sorted_players.length && desired < max_players; ++desired) {
699 if (desired != sorted_players[desired]) {
700 return desired;
701 }
702 }
703 if (sorted_players.length < max_players) {
704 return sorted_players.length;
705 }
706 return null;
707 }
708
709 function p8_assign_gamepad_to_player(gamepad_index, player_index) {
710 p8_unassign_gamepad(gamepad_index);
711 pico8_gamepads_mapping[gamepad_index] = player_index;
712 }
713
714
715
716 function p8_convert_standard_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) {
717 // Given a gamepad object, return:
718 // {
719 // button_state: the binary encoded Pico 8 button state
720 // menu_button: true if any menu-mapped button was pressed
721 // any_button: true if any button was pressed, including d-pad
722 // buttons and unmapped buttons
723 // }
724 if (!gamepad || !gamepad.axes || !gamepad.buttons) {
725 return {
726 button_state: 0,
727 menu_button: false,
728 any_button: false
729 };
730 }
731 function button_state_from_axis(axis, low_state, high_state, default_state) {
732 if (axis && axis < -axis_threshold) return low_state;
733 if (axis && axis > axis_threshold) return high_state;
734 return default_state;
735 }
736 var axes_actions = [
737 button_state_from_axis(gamepad.axes[0], P8_DPAD_LEFT, P8_DPAD_RIGHT, P8_NO_ACTION),
738 button_state_from_axis(gamepad.axes[1], P8_DPAD_UP, P8_DPAD_DOWN, P8_NO_ACTION),
739 ];
740
741 var button_actions = gamepad.buttons.map(function (button, index) {
742 var pressed = button.value > button_threshold || button.pressed;
743 if (!pressed) return P8_NO_ACTION;
744 return P8_BUTTON_MAPPING[index] || P8_NO_ACTION;
745 });
746
747 var all_actions = axes_actions.concat(button_actions);
748
749 var menu_button = button_actions.some(function (action) { return action.action == 'menu'; });
750 var button_state = (all_actions
751 .filter(function (a) { return a.action == 'button'; })
752 .map(function (a) { return a.code; })
753 .reduce(function (result, code) { return result | code; }, 0)
754 );
755 var any_button = gamepad.buttons.some(function (button) {
756 return button.value > button_threshold || button.pressed;
757 });
758
759 any_button |= button_state; //jww: include axes 0,1 as might be first intended action
760
761 return {
762 button_state,
763 menu_button,
764 any_button
765 };
766 }
767
768 // jww: pico-8 0.2.1 version for unmapped gamepads, following p8_convert_standard_gamepad_to_button_state
769 // axes 0,1 & buttons 0,1,2,3 are reasonably safe. don't try to read dpad.
770 // menu buttons are unpredictable, but use 6..8 anyway (better to have a weird menu button than none)
771
772 function p8_convert_unmapped_gamepad_to_button_state(gamepad, axis_threshold, button_threshold) {
773
774 if (!gamepad || !gamepad.axes || !gamepad.buttons) {
775 return {
776 button_state: 0,
777 menu_button: false,
778 any_button: false
779 };
780 }
781
782 var button_state = 0;
783
784 if (gamepad.axes[0] && gamepad.axes[0] < -axis_threshold) button_state |= 0x1;
785 if (gamepad.axes[0] && gamepad.axes[0] > axis_threshold) button_state |= 0x2;
786 if (gamepad.axes[1] && gamepad.axes[1] < -axis_threshold) button_state |= 0x4;
787 if (gamepad.axes[1] && gamepad.axes[1] > axis_threshold) button_state |= 0x8;
788
789 // buttons: first 4 taken to be O/X, 6..8 taken to be menu button
790
791 for (j = 0; j < gamepad.buttons.length; j++)
792 if (gamepad.buttons[j].value > 0 || gamepad.buttons[j].pressed)
793 {
794 if (j < 4)
795 button_state |= (0x10 << (((j+1)/2)&1)); // 0 1 1 0 -- A,X -> O,X on xbox360
796 else if (j >= 6 && j <= 8)
797 button_state |= 0x40;
798 }
799
800 var menu_button = button_state & 0x40;
801
802 var any_button = gamepad.buttons.some(function (button) {
803 return button.value > button_threshold || button.pressed;
804 });
805
806 any_button |= button_state; //jww: include axes 0,1 as might be first intended action
807
808 return {
809 button_state,
810 menu_button,
811 any_button
812 };
813 }
814
815
816 // gamepad https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
817 // (sets bits in pico8_buttons[])
818 function p8_update_gamepads() {
819 var axis_threshold = 0.3;
820 var button_threshold = 0.5; // Should be unnecessary, we should be able to trust .pressed
821 var max_players = 8;
822 var gps = navigator.getGamepads() || navigator.webkitGetGamepads();
823
824 if (!gps) return;
825
826 // In Chrome, gps is iterable but it's not an array.
827 gps = Array.from(gps);
828
829 pico8_gamepads.count = gps.length;
830 while (gps.length > pico8_gamepads_mapping.length) {
831 pico8_gamepads_mapping.push(null);
832 }
833
834 var menu_button = false;
835 var gamepad_states = gps.map(function (gp) {
836 return (gp && gp.mapping == "standard") ?
837 p8_convert_standard_gamepad_to_button_state(gp, axis_threshold, button_threshold) :
838 p8_convert_unmapped_gamepad_to_button_state(gp, axis_threshold, button_threshold);
839 });
840
841 // Unassign disconnected gamepads.
842 // gps.forEach(function (gp, i) { if (gp && !gp.connected) { p8_unassign_gamepad(i); }});
843 gps.forEach(function (gp, i) { if (!gp || !gp.connected) { p8_unassign_gamepad(i); }}); // https://www.lexaloffle.com/bbs/?pid=87132#p
844
845
846 // Assign unassigned gamepads when any button is pressed.
847 gamepad_states.forEach(function (state, i) {
848 if (state.any_button && pico8_gamepads_mapping[i] == null) {
849 var first_free_player = p8_first_player_without_gamepad(max_players);
850 p8_assign_gamepad_to_player(i, first_free_player);
851 }
852 });
853
854 // Update pico8_buttons array.
855 gamepad_states.forEach(function (gamepad_state, i) {
856 if (pico8_gamepads_mapping[i] != null) {
857 pico8_buttons[pico8_gamepads_mapping[i]] = gamepad_state.button_state;
858 }
859 });
860
861 // Update menu button.
862 // Pico 8 only recognises the menu button on the first player, so we
863 // press it when any gamepad has pressed a button mapped to menu.
864 if (gamepad_states.some(function (state) { return state.menu_button; })) {
865 pico8_buttons[0] |= 0x40;
866 }
867
868 requestAnimationFrame(p8_update_gamepads);
869 }
870 requestAnimationFrame(p8_update_gamepads);
871
872 // End of gamepad code
873
874
875 // key blocker. prevent cursor keys from scrolling page while playing cart.
876 // also don't act on M, R so that can mute / reset cart
877 document.addEventListener('keydown',
878 function (event) {
879 event = event || window.event;
880 if (!p8_is_running) return;
881 if (pico8_state.has_focus == 1)
882 if ([32, 37, 38, 39, 40, 77, 82, 80, 9].indexOf(event.keyCode) > -1) // block cursors, M R P, tab
883 if (event.preventDefault) event.preventDefault();
884 },{passive: false});
885
886 // when using codo_textarea to determine focus, need to explicitly hand focus back when clicking a p8_menu_button
887 function p8_give_focus()
888 {
889 el = (typeof codo_textarea === 'undefined') ? document.getElementById("codo_textarea") : codo_textarea;
890 if (el)
891 {
892 el.focus();
893 el.select();
894 }
895 }
896
897 function p8_request_fullscreen() {
898
899 var is_fullscreen=(document.fullscreenElement || document.mozFullScreenElement || document.webkitIsFullScreen || document.msFullscreenElement);
900
901 if (is_fullscreen)
902 {
903 if (document.exitFullscreen) {
904 document.exitFullscreen();
905 } else if (document.webkitExitFullscreen) {
906 document.webkitExitFullscreen();
907 } else if (document.mozCancelFullScreen) {
908 document.mozCancelFullScreen();
909 } else if (document.msExitFullscreen) {
910 document.msExitFullscreen();
911 }
912 return;
913 }
914
915 var el = document.getElementById("p8_playarea");
916
917 if ( el.requestFullscreen ) {
918 el.requestFullscreen();
919 } else if ( el.mozRequestFullScreen ) {
920 el.mozRequestFullScreen();
921 } else if ( el.webkitRequestFullScreen ) {
922 el.webkitRequestFullScreen( Element.ALLOW_KEYBOARD_INPUT );
923 }
924 }
925
926 </script>
927
928 <STYLE TYPE="text/css">
929 <!--
930 .p8_menu_button{
931 opacity:0.3;
932 padding:4px;
933 display:table;
934 width:24px;
935 height:24px;
936 float:right;
937 }
938
939 @media screen and (min-width:512px) {
940 .p8_menu_button{
941 width:24px; margin-left:12px; margin-bottom:8px;
942 }
943 }
944 .p8_menu_button:hover{
945 opacity:1.0;
946 cursor:pointer;
947 }
948
949 canvas{
950 image-rendering: optimizeSpeed;
951 image-rendering: -moz-crisp-edges;
952 image-rendering: -webkit-optimize-contrast;
953 image-rendering: optimize-contrast;
954 image-rendering: pixelated;
955 -ms-interpolation-mode: nearest-neighbor;
956 border: 0px;
957 cursor: none;
958 }
959
960
961 .p8_start_button{
962 cursor:pointer;
963 background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAC1klEQVR4Ae3dP2vjMBjHcVFuO8hgnEl7bNWDoUOXFEzwntIhgY7C70BkyCay3XBvofQl9DXcqyh0v/WmzqVDwebS1HHS2LKV7wcNdpx/+j16pFWgXUaKPK6GkUTirgZHp58m0Ww6n03naRKd9ukZ1eDo9BdKpklUjoX674u+8xRCiIsmb5q8vV5d31xd30zeXk/7FPsLcHl739ItGrHrVUu39bJA0QHiq3N16yJc2pqne79wpz//nln94uPw3Ip15/WhT4fKSJHH1TCyF/vSeSlr4Cp9CCNJHwAnE7qldUEIOCkjRR5Xw0gicVcD0ndZA9L3TBYoQhgIrQtCAIATMFLkcTWMJBJ3NSB9lzVolr7WBWkBAIA+MlLkcTWMJBJ3NSB9lzUgfbiUBWrve+x6RVCAL0bj/U0PHMpIkcfVMJJI3NWA9F3WgPTxWZpEhADWGuAnrQvv5mSkyONqGEmZ3dXAi/SzQA2zBqx9AADQOq2L1r7bSJHH1TCSuDtX1qDH6be5BntSg9r07XrFQgXadxat1p9JsrMBAzcaK0JA59Ikmk3ns+k8TSJvJxkubT//2ELJNInKsVDSt+nb9arPK2Oh5EJJrQuti4/rAS3riyZv2vz63ec5XN7e19x2QOvi6M/+8GN7vHz+KeIH8TQRd3+7//XHx4cTdMB3yuj2+BVCbMSLeJqIu5fNU/UiOipAGbcVk61XAMA/WaAIYQ+7Xh36kXBpyQ0AAPhqNFaEAIBND3WygNTQUJpEhOC/cGm9/C14WgOtC6oIAADcSZOIEAAAtUZjxZEFH9j16lymmgW96Fp/DGX/6fR/hkvLyqDrAQD9Pq5xBK0LQgDgxGisCAGDEi4trYd+S5OIwoMVABzQFx9j67q8bdhTW5/FMRtTWYbPG9bOZL/6LA7ugL3d0KR7zqEAWXDqCX5exTuf1nTAWRWgiy2o4QFwRMeg380I4Lu76OC8A7fewrw4RiyeAAAAEHRFWHRMb2RlUE5HADIwMTEwMjIx41m2wQAAAABJRU5ErkJggg==");
964 -repeat center;
965 -webkit-background-size:cover; -moz-background-size:cover; -o-background-size:cover; background-size:cover;
966 }
967
968 .button_gfx{
969 stroke-width:2;
970 stroke: #ffffff;
971 stroke-opacity:0.4;
972 fill-opacity:0.2;
973 fill:black;
974 }
975
976 .button_gfx_icon{
977 stroke-width:3;
978 stroke: #909090;
979 stroke-opacity:0.7;
980 fill:none;
981 }
982
983 -->
984 </STYLE>
985
986 </head>
987
988 <body style="padding:0px; margin:0px; background-color:#222; color:#ccc">
989 <div id="body_0"> <!-- hide this when playing in mobile (p8_touch_detected) so that elements don't affect layout -->
990
991
992 <!-- Add any content above the cart here -->
993
994
995 <div id="p8_frame_0" style="max-width:800px; max-height:800px; margin:auto;"> <!-- double function: limit size, and display only this div for touch devices -->
996 <div id="p8_frame" style="display:flex; width:100%; max-width:95vw; height:100vw; max-height:95vh; margin:auto;">
997
998 <div id="p8_menu_buttons_touch" style="position:absolute; width:100%; z-index:10; left:0px;">
999 <div class="p8_menu_button" id="p8b_full" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_request_fullscreen();"></div>
1000 <div class="p8_menu_button" id="p8b_sound" style="float:left;margin-left:10px" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div>
1001 <div class="p8_menu_button" id="p8b_close" style="float:right; margin-right:10px" onClick="p8_close_cart();"></div>
1002 </div>
1003
1004 <div id="p8_container"
1005 style="margin:auto; display:table;"
1006 onclick="p8_create_audio_context(); p8_run_cart();">
1007
1008 <div id="p8_start_button" class="p8_start_button" style="width:100%; height:100%; display:flex;">
1009 <img width=80 height=80 style="margin:auto;"
1010 src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAABpklEQVR42u3au23DQBCEYUXOXIGKcujQXUgFuA0XIKgW90Q9oEAg+Ljd27vd2RsCf058gEDqhofPj+OB6SMCAQlIQAIyAhKQgARkBAQDnM6XSRsB7/2e/tSA0//12fCAKsQX3ntDA4oRFwBRIc0AixE38BAhTQGLEAsBUSDNAXcRhYDRIZsAPlp99VECRoXsDpgN0g0wC6Q7IDpkGEBUyG6A0+vKBtkdMBukG2AWSHdAdMgwgKiQ4QDRIMMCokCGB4wOCQPYFVKw2cABNocUjl6wgE0gFashPKAZpHJ2TQNYBVmxW6cDFENWDv9pAUshCVgJScBKSAISkD9hPkT4GkNAMdzepyj8Kye852EBLe51CZHHWQK4JcThD1SlcHPEYY/0a+A0n6SkGZV6w6WZNb3g4Id1b7hwgGhwYQBR4dwB0eHcALPAdQfMBhcOEA0uDCAqnDsgOpwbYBa4poA/31+rZYFrBriFpwGMCtcEcA9PAhgdzhywBK8EEQXOFFCCtwaIBmcGKMWbI6LCmQBq8R6hw5kAMgISkIAEJCAjIAEJSEBGQI9ukV7lRn9nD+gAAAAASUVORK5CYII="/>
1011 </div>
1012
1013 <div id="p8_playarea" style="display:none; margin:auto;
1014 -webkit-user-select:none; -moz-user-select: none; user-select: none; -webkit-touch-callout:none;
1015 ">
1016
1017 <div id="touch_controls_background"
1018 style=" pointer-events:none; display:none; background-color:#000;
1019 position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh">
1020  
1021 </div>
1022
1023 <div style="display:flex; position:relative">
1024 <!-- pointer-events turned off for mobile in p8_update_layout because need for desktop mouse -->
1025 <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault();" >
1026 </canvas>
1027 <div class=p8_menu_buttons id="p8_menu_buttons" style="margin-left:10px;">
1028 <div class="p8_menu_button" style="position:absolute; bottom:125px" id="p8b_controls" onClick="p8_give_focus(); Module.pico8ToggleControlMenu();"></div>
1029 <div class="p8_menu_button" style="position:absolute; bottom:90px" id="p8b_pause" onClick="p8_give_focus(); Module.pico8TogglePaused(); p8_update_layout_hash = -22;"></div>
1030 <div class="p8_menu_button" style="position:absolute; bottom:55px" id="p8b_sound" onClick="p8_give_focus(); p8_create_audio_context(); Module.pico8ToggleSound();"></div>
1031 <div class="p8_menu_button" style="position:absolute; bottom:20px" id="p8b_full" onClick="p8_give_focus(); p8_request_fullscreen();"></div>
1032 </div>
1033 </div>
1034
1035
1036 <!-- display after first layout update -->
1037 <div id="touch_controls_gfx"
1038 style=" pointer-events:none; display:table;
1039 position:fixed; top:0px; left:0px; border:0; width:100vw; height:100vh">
1040
1041 <img src="" id="controls_right_panel" style="position:absolute; opacity:0.5;">
1042 <img src="" id="controls_left_panel" style="position:absolute; opacity:0.5;">
1043
1044
1045 </div> <!-- touch_controls_gfx -->
1046
1047 <!-- used for clipboard access & keyboard input; displayed and used by PICO-8 only once needed. can be safely removed if clipboard / key presses not needed. -->
1048 <!-- (needs to be inside p8_playarea so that it still works under Chrome when fullscreened) -->
1049 <textarea id="codo_textarea" class="emscripten" style="display:none; position:absolute; left:-9999px; height:0px; overflow:hidden"></textarea>
1050
1051 </div> <!--p8_playarea -->
1052
1053 </div> <!-- p8_container -->
1054
1055 </div> <!-- p8_frame -->
1056 </div> <!-- p8_frame_0 size limit -->
1057
1058 <script type="text/javascript">
1059
1060 p8_update_layout();
1061 p8_update_button_icons();
1062
1063 var canvas = document.getElementById("canvas");
1064 Module = {};
1065 Module.canvas = canvas;
1066
1067 // from @ultrabrite's shell: test if an AudioContext can be created outside of an event callback.
1068 // If it can't be created, then require pressing the start button to run the cartridge
1069
1070 if (p8_autoplay)
1071 {
1072 var temp_context = new AudioContext();
1073 temp_context.onstatechange = function ()
1074 {
1075 if (temp_context.state=='running')
1076 {
1077 p8_run_cart();
1078 temp_context.close();
1079 }
1080 };
1081 }
1082
1083 // pointer lock request needs to be inside a canvas interaction event
1084 // pico8_state.request_pointer_lock is true when 0x5f2d bit 0 and bit 2 are set -- poke(0x5f2d,0x5)
1085 // note on mouse acceleration for future: // https://github.com/w3c/pointerlock/pull/49
1086 canvas.addEventListener("click", function()
1087 {
1088 if (!p8_touch_detected)
1089 if (pico8_state.request_pointer_lock)
1090 canvas.requestPointerLock();
1091 });
1092
1093 </script>
1094
1095
1096
1097 <!-- Add content below the cart here -->
1098
1099
1100
1101
1102 </div> <!-- body_0 -->
1103 </body></html>