diff --git a/README.md b/README.md index 271b01c1..91bcfc79 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ The **quadplay✜** fantasy console by [CasualEffects](https://casual-effects.com) is now in public beta. -- [Play online](https://morgan3d.github.io/quadplay/console/index.html) -- [See the IDE](https://morgan3d.github.io/quadplay/console/index.html?IDE=1&game=quad://games/quadpaddle) (editing disabled) +- [Play online](https://morgan3d.github.io/quadplay/console/quadplay.html) +- [See the IDE](https://morgan3d.github.io/quadplay/console/quadplay.html?IDE=1&game=quad://games/quadpaddle) (editing disabled) - [Read the manual](https://morgan3d.github.io/quadplay/doc/manual.md.html) - [Download the console](https://github.com/morgan3d/quadplay/archive/master.zip) diff --git a/console/quadplay-host.js b/console/quadplay-host.js index 53317663..21c3da53 100644 --- a/console/quadplay-host.js +++ b/console/quadplay-host.js @@ -248,15 +248,16 @@ function getIdealGamepads() { let pad = gamepads[i]; if (pad && pad.connected) { // Construct a simplified web gamepad API - let mypad = {axes:[0, 0, 0, 0], buttons:[]}; - const axisRemap = gamepadAxisRemap[pad.id] || gamepadAxisRemap.identity; + let mypad = {axes:[0, 0, 0, 0], buttons:[], analogAxes:[0,0,0,0]}; + const axisRemap = gamepadAxisRemap[pad.id] || gamepadAxisRemap.identity; for (let a = 0; a < Math.min(4, pad.axes.length); ++a) { const axis = pad.axes[axisRemap[a]]; mypad.axes[a] = (Math.abs(axis) > deadZone) ? Math.sign(axis) : 0; + mypad.analogAxes[a] = axis; } // Process all 17 buttons/axes as digital buttons first - const buttonRemap = gamepadButtonRemap[pad.id] || gamepadButtonRemap.identity; + const buttonRemap = gamepadButtonRemap[pad.id] || gamepadButtonRemap.identity; for (let b = 0; b < 17; ++b) { const button = pad.buttons[buttonRemap[b]]; // Different browsers follow different APIs for the value of buttons @@ -612,7 +613,7 @@ function submitFrame() { function updateInput() { - const axes = 'xy', buttons = 'abcdpq', BUTTONS = 'ABCDPQ'; + const axes = 'xy', AXES = 'XY', buttons = 'abcdpq', BUTTONS = 'ABCDPQ'; // HTML gamepad indices of corresponding elements of the buttons array // A, B, C, D, P, Q @@ -639,15 +640,16 @@ function updateInput() { altPrevRealGamepad = (player === 2) ? prevRealGamepadState[0] : (player === 3) ? prevRealGamepadState[0] : undefined; */ // Have player 0 physical alt controls set player 1 virtual buttons - const altRealGamepad = (player === 1) ? gamepadArray[0] : undefined, + const altRealGamepad = (player === 1) ? gamepadArray[0] : undefined, altPrevRealGamepad = (player === 1) ? prevRealGamepadState[0] : undefined; // Axes for (let a = 0; a < axes.length; ++a) { const axis = axes[a]; + const AXIS = '_analog' + AXES[a]; const pos = '+' + axis, neg = '-' + axis; const old = pad[axis]; - const scale = (axis == 'x') ? Runtime._scaleX : Runtime._scaleY; + const scale = (axis === 'x') ? Runtime._scaleX : Runtime._scaleY; if (map) { // Keyboard controls @@ -664,7 +666,13 @@ function updateInput() { pad[axis] = pad[axis + axis] = 0; } - if (realGamepad && (realGamepad.axes[a] !== 0)) { pad[axis] = realGamepad.axes[a] * scale; } + pad[AXIS] = pad[axis]; + + if (realGamepad && (realGamepad.axes[a] !== 0)) { + pad[axis] = realGamepad.axes[a] * scale; + pad[AXIS] = realGamepad.analogAxes[a] * scale; + } + if (realGamepad && (prevRealGamepad.axes[a] !== realGamepad.axes[a])) { pad[axis + axis] = realGamepad.axes[a] * scale; } @@ -675,13 +683,13 @@ function updateInput() { // to controller[1] d-pad (axes 0 + 1) for "dual stick" controls if (otherPad.axes[a + 2] !== 0) { pad[axis] = otherPad.axes[a + 2] * scale; + pad[AXIS] = otherPad.analogAxes[a + 2] * scale; } if (otherPad.axes[a + 2] !== otherPad.axes[a + 2]) { pad[axis + axis] = otherPad.axes[a + 2] * scale; } } // dual-stick - pad['d' + axis] = pad[axis] - old; } @@ -701,7 +709,7 @@ function updateInput() { const i = buttonIndex[b], j = altButtonIndex[b]; const isPressed = (realGamepad && realGamepad.buttons[i]) || (altRealGamepad && altRealGamepad.buttons[j]); - const wasPressed = (prevRealGamepad && prevRealGamepad.buttons[i]) || + const wasPressed = (prevRealGamepad && prevRealGamepad.buttons[i]) || (altPrevRealGamepad && altPrevRealGamepad.buttons[j]); if (isPressed) { pad[button] = 1; } @@ -733,9 +741,9 @@ function updateInput() { // loop so that the alternative buttons for player 2 are not // immediately overrident during player 1's processing. for (let player = 0; player < 4; ++player) { - if (gamepadArray[player]) { + if (gamepadArray[player]) { prevRealGamepadState[player] = gamepadArray[player]; - } + } } // Reset the just-pressed state diff --git a/console/quadplay-ide.js b/console/quadplay-ide.js index 134373c8..eb098765 100644 --- a/console/quadplay-ide.js +++ b/console/quadplay-ide.js @@ -610,8 +610,16 @@ function deviceControl(cmd) { case "stopGIFRecording": stopGIFRecording(); break; case "takeScreenshot": downloadScreenshot(); break; case "startPreviewRecording": startPreviewRecording(); break; + case "getAnalogAxes": + { + const i = clamp(parseInt(arguments[1]), 0, 3); + const pad = Runtime.pad[i]; + return {x: pad._analogX, y: pad._analogY}; + break; + } + case "setPadType": - + { const i = arguments[1]; const type = arguments[2]; const prompt = controlSchemeTable[type]; @@ -620,6 +628,7 @@ function deviceControl(cmd) { Runtime.pad[i].type = type; Runtime.pad[i].prompt = prompt; break; + } } } @@ -1538,6 +1547,8 @@ function setFramebufferSize(w, h) { // The layout may need updating as well setTimeout(onResize, 0); + setTimeout(onResize, 250); + setTimeout(onResize, 1250); } @@ -2149,33 +2160,33 @@ function loadGameIntoIDE(url, callback) { // Let the boot screen show before we add to it setTimeout(function() { { - let serverURL = location.origin + location.pathname; - // Remove common subexpression for shorter URLs - if (url.substring(0, serverURL.length) === serverURL) { - url = url.substring(serverURL.length); + let serverURL = location.origin + location.pathname; + // Remove common subexpression for shorter URLs + if (url.substring(0, serverURL.length) === serverURL) { + url = url.substring(serverURL.length); + } + + // Remove redundant filename for shorterURLs + url = url.replace(/([^\/:=&]+)\/([^\/:=&]+?)\.game\.json$/, function (match, path, filename) { + return (path === filename) ? path + '/' : match; + }); + + serverURL += '?game=' + url; + qrcode.makeCode(serverURL); + document.getElementById('serverURL').innerHTML = + `${serverURL}`; } - - // Remove redundant filename for shorterURLs - url = url.replace(/([^\/:=&]+)\/([^\/:=&]+?)\.game\.json$/, function (match, path, filename) { - return (path === filename) ? path + '/' : match; - }); - - serverURL += '?game=' + url; - qrcode.makeCode(serverURL); - document.getElementById('serverURL').innerHTML = - `${serverURL}`; - } - document.getElementById('playButton').enabled = false; - onLoadFileStart(url); - afterLoadGame(url, function () { - onLoadFileComplete(url); - hideBootScreen(); - console.log('Loading complete.'); - setFramebufferSize(gameSource.json.screenSize.x, gameSource.json.screenSize.y); - createProjectWindow(gameSource); - let resourcePane = document.getElementById('resourcePane'); - resourcePane.innerHTML = ` + document.getElementById('playButton').enabled = false; + onLoadFileStart(url); + afterLoadGame(url, function () { + onLoadFileComplete(url); + hideBootScreen(); + console.log('Loading complete.'); + setFramebufferSize(gameSource.json.screenSize.x, gameSource.json.screenSize.y); + createProjectWindow(gameSource); + let resourcePane = document.getElementById('resourcePane'); + resourcePane.innerHTML = `
Resource Limits


@@ -2187,19 +2198,19 @@ function loadGameIntoIDE(url, callback) { Source Statements${resourceStats.sourceStatements}/8192(${Math.round(resourceStats.sourceStatements*100/8192)}%) Sounds${resourceStats.sounds}/128(${Math.round(resourceStats.sounds*100/128)}%) `; - document.getElementById('playButton').enabled = true; - - const modeEditor = document.getElementById('modeEditor'); - if (modeEditor.style.visibility === 'visible') { - // Update the editor - visualizeModes(modeEditor); - } - - aceEditor.gotoLine(0, 0, false); - aceEditor.scrollToLine(0, false, false, undefined); - hideWaitDialog(); - -appendToBootScreen(` + document.getElementById('playButton').enabled = true; + + const modeEditor = document.getElementById('modeEditor'); + if (modeEditor.style.visibility === 'visible') { + // Update the editor + visualizeModes(modeEditor); + } + + aceEditor.gotoLine(0, 0, false); + aceEditor.scrollToLine(0, false, false, undefined); + hideWaitDialog(); + + appendToBootScreen(` QuadOS ROM: 256269 bytes Runtime ROM: 159754 bytes @@ -2216,13 +2227,13 @@ Checking game pad input…OK Starting… `); - if (callback) { callback(); } - }, function (e) { - hideBootScreen(); - setErrorStatus('Loading ' + url + ' failed: ' + e); - onStopButton(); - hideWaitDialog(); - }); + if (callback) { callback(); } + }, function (e) { + hideBootScreen(); + setErrorStatus('Loading ' + url + ' failed: ' + e); + onStopButton(); + hideWaitDialog(); + }); }, 15); } diff --git a/console/quadplay-runtime.js b/console/quadplay-runtime.js index cdd2c749..6f25987e 100644 --- a/console/quadplay-runtime.js +++ b/console/quadplay-runtime.js @@ -1038,7 +1038,9 @@ function _makePad(index) { lt: '⍇', dn: '⍗', rt: '⍈' - }) + }), + _analogX: 0, + _analogY: 0 }); } diff --git a/console/index.html b/console/quadplay.html similarity index 100% rename from console/index.html rename to console/quadplay.html diff --git a/doc/manual.md.html b/doc/manual.md.html index d5bd3614..0f19d5b6 100644 --- a/doc/manual.md.html +++ b/doc/manual.md.html @@ -2682,7 +2682,7 @@ : Reserved function for executing device-specific features such as the game launcher, GPIO, and alternative input devices. `command` is a string telling the host which control feature to execute, and the - remaining arguments are passed to it. All `deviceControl` commands + remaining arguments are passed to it. All `deviceControl()` commands may change in future releases and may not work on any given platform. - `"startGIFRecording"` Begin GIF recording. @@ -2692,7 +2692,8 @@ terminates automatically after a set time. - `"takeScreenshot"` Take a screenshot and download it to the local disk. - `"setPadType", index, "type"` Set `pad[index].type` and the corresponding prompts. - + - `"getAnalogAxes", index` Return an `xy()` of the analog axis values for `pad[index]`, without D-pad snapping. + Needed for accessing Atari paddles, car pedals, steering wheels, and flight stick throttles in a meaningful way. `drawBounds(e, color (= gray(60%)), recurse (= true))` : Render a debugging view of the bounds of an entity or other object @@ -2987,6 +2988,8 @@ ![`quad://fonts/nano-3.font.json`](../fonts/nano-3.png) +![`quad://fonts/nanob-3.font.json`](../fonts/nanob-3.png) + Sounds ------------------------------------------------------------------------------------ @@ -4793,6 +4796,14 @@ Changes for both the specification and implementation in each release. +2019 August 17: Beta update 9 + +- Added `nanob-3.font.json` (like nano-3, but with thinner L and I characters) +- Patch for emulator view resizing for new dimensions on slower browsers +- Added `deviceControl()` `"getAnalogAxes" command +- Renamed `index.html` to `quadplay.html` for improved itch.io compatibility + + 2019 August 16: Beta update 8 - Allowed `for` and `with` variable lists to be split across multiple lines diff --git a/fonts/nano-3.font.json b/fonts/nano-3.font.json index 381c949d..87531b63 100644 --- a/fonts/nano-3.font.json +++ b/fonts/nano-3.font.json @@ -1,7 +1,7 @@ { "url": "nano-3.png", "license": "© 2019 Morgan McGuire. Licensed as CC BY 4.0", - "charSize": {"x": 4, "y": 4}, + "charSize": {"x": 4, "y": 5}, "letterSpacing": {"x": 1, "y": 0}, "baseline": 2 } diff --git a/fonts/nano-3.png b/fonts/nano-3.png index 10e13a8b..6ab6d284 100644 Binary files a/fonts/nano-3.png and b/fonts/nano-3.png differ diff --git a/fonts/nano-4.png b/fonts/nano-4.png index 71afd080..4da860e4 100644 Binary files a/fonts/nano-4.png and b/fonts/nano-4.png differ diff --git a/fonts/nanob-3.font.json b/fonts/nanob-3.font.json new file mode 100644 index 00000000..ec581407 --- /dev/null +++ b/fonts/nanob-3.font.json @@ -0,0 +1,7 @@ +{ + "url": "nanob-3.png", + "license": "© 2019 Morgan McGuire. Licensed as CC BY 4.0", + "charSize": {"x": 4, "y": 5}, + "letterSpacing": {"x": 1, "y": 0}, + "baseline": 2 +} diff --git a/fonts/nanob-3.png b/fonts/nanob-3.png new file mode 100644 index 00000000..887d61d4 Binary files /dev/null and b/fonts/nanob-3.png differ diff --git a/tools/pyxlscript-mode.el b/tools/pyxlscript-mode.el index 00ee20dc..405aba0a 100644 --- a/tools/pyxlscript-mode.el +++ b/tools/pyxlscript-mode.el @@ -46,7 +46,7 @@ "makeEntity" "drawEntity" "overlaps" "updateEntityChildren" "physicsStepEntity" "now" "gameFrames" "modeFrames" "findMapPath" "findPath" "gray" "rgb" "rgba" "hsv" "hsva" - "call" + "call" "setPostEffects" "resetPostEffects" "abs" "acos" "atan" "asin" "sign" "signNonZero" "cos" "clamp" "hash" "lerp" "log" "log2" "log10" "loop" "min" "max" "mid" "noise" "oscillate" "overlap" "pow" "rndInt" "rndDisk" "rndSquare" "rndValue" "rnd" "ξ" "sgn" "sqrt" "sin" "srand" "tan" "clone" "copy" "drawPreviousMode" "cross" "direction" "dot" "equivalent" "magnitude" "maxComponent" "minComponent" "xy" "xyz" "fastRemoveKey" "find" "keys" "removeKey" "size" "substring" "sort" "resize" "push" "pop" "removeValues" "pad" "joy" "round" "floor" "ceil"