shmup.html 42 KB

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