2024-03-29 22:23:01 -07:00
|
|
|
import { assertNever } from "@firebox/tsutil";
|
2024-03-29 20:23:14 -07:00
|
|
|
import { pngToRom } from "./pngToRom";
|
2024-03-31 12:13:27 -07:00
|
|
|
import { RenderCart, renderCart as rawRenderCart } from "./rawRenderCart";
|
2024-03-29 02:02:40 -07:00
|
|
|
|
2024-03-31 12:13:27 -07:00
|
|
|
export type PicoCart = {
|
2024-03-29 02:02:40 -07:00
|
|
|
name: string;
|
2024-03-29 22:23:01 -07:00
|
|
|
src: string;
|
|
|
|
} | {
|
|
|
|
name: string;
|
|
|
|
rom: number[];
|
2024-03-29 02:02:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
type PlayerButtons = {
|
|
|
|
left: boolean;
|
|
|
|
right: boolean;
|
|
|
|
up: boolean;
|
|
|
|
down: boolean;
|
|
|
|
o: boolean;
|
|
|
|
x: boolean;
|
|
|
|
menu: boolean;
|
|
|
|
}
|
|
|
|
|
2024-03-31 12:13:27 -07:00
|
|
|
export type PicoPlayerHandle = {
|
|
|
|
raw: ReturnType<RenderCart>;
|
|
|
|
rawModule: unknown;
|
2024-03-29 02:02:40 -07:00
|
|
|
// external things
|
|
|
|
readonly canvas: HTMLCanvasElement;
|
|
|
|
|
|
|
|
// i/o
|
|
|
|
setButtons: (buttons: PlayerButtons[]) => void;
|
|
|
|
setMouse: (mouse: {
|
|
|
|
x: number;
|
|
|
|
y: number;
|
|
|
|
leftClick: boolean;
|
|
|
|
rightClick: boolean;
|
|
|
|
}) => void;
|
|
|
|
setGamepadCount: (count: number) => void;
|
2024-04-04 08:58:39 -07:00
|
|
|
gpio: (
|
|
|
|
number[]
|
|
|
|
// & {subscribe: (f: (gpio: number[]) => void) => void}
|
|
|
|
); // read + write (should be 256-tuple)
|
2024-03-29 02:02:40 -07:00
|
|
|
|
2024-03-31 12:13:27 -07:00
|
|
|
// state
|
2024-03-29 02:02:40 -07:00
|
|
|
readonly state: {
|
2024-03-31 12:13:27 -07:00
|
|
|
frameNumber: number;
|
|
|
|
isPaused: boolean;
|
|
|
|
hasFocus: boolean;
|
|
|
|
requestPointerLock: boolean;
|
|
|
|
requirePageNavigateConfirmation: boolean;
|
|
|
|
showDpad: boolean;
|
|
|
|
shutdownRequested: boolean;
|
|
|
|
soundVolume: number;
|
2024-03-29 02:02:40 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
// misc?
|
|
|
|
setTouchDetected: (touchDetected: boolean) => void;
|
|
|
|
dropCart: (cart: PicoCart) => void;
|
|
|
|
|
2024-03-29 20:23:14 -07:00
|
|
|
// Module
|
|
|
|
toggleSound: () => void;
|
|
|
|
toggleControlMenu: () => void;
|
|
|
|
togglePaused: () => void;
|
|
|
|
// TODO: rename these two better (what do they do??)
|
|
|
|
modDragOver: () => void;
|
|
|
|
modDragStop: () => void;
|
2024-03-29 02:02:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const bitfield = (...args: boolean[]): number => {
|
|
|
|
if (!args.length) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return (args[0]?1:0)+2*bitfield(...args.slice(1));
|
|
|
|
}
|
|
|
|
|
2024-03-29 22:23:01 -07:00
|
|
|
const getRom = async (cart: PicoCart) => {
|
|
|
|
if ("src" in cart) {
|
|
|
|
return await pngToRom(cart.src);
|
|
|
|
} else if ("rom" in cart) {
|
|
|
|
return cart.rom;
|
2024-03-29 20:23:14 -07:00
|
|
|
}
|
2024-03-29 22:23:01 -07:00
|
|
|
assertNever(cart);
|
|
|
|
}
|
2024-03-29 20:23:14 -07:00
|
|
|
|
2024-03-29 22:23:01 -07:00
|
|
|
export const makePicoConsole = async (props: {
|
|
|
|
canvas?: HTMLCanvasElement;
|
2024-03-31 21:25:54 -07:00
|
|
|
codoTextarea?: HTMLTextAreaElement;
|
2024-03-29 22:23:01 -07:00
|
|
|
audioContext?: AudioContext;
|
|
|
|
carts: PicoCart[];
|
|
|
|
}): Promise<PicoPlayerHandle> => {
|
2024-03-31 21:25:54 -07:00
|
|
|
const {carts, canvas = document.createElement("canvas"), codoTextarea = document.createElement("textarea"), audioContext = new AudioContext()} = props;
|
2024-03-29 22:23:01 -07:00
|
|
|
canvas.style.imageRendering = "pixelated";
|
2024-03-31 21:25:54 -07:00
|
|
|
codoTextarea.style.display="none";
|
|
|
|
codoTextarea.style.position="fixed";
|
|
|
|
codoTextarea.style.left="-9999px";
|
|
|
|
codoTextarea.style.height="0px";
|
|
|
|
codoTextarea.style.overflow="hidden";
|
2024-04-01 00:22:52 -07:00
|
|
|
const Module = {canvas, keyboardListeningElement: canvas};
|
2024-03-29 22:23:01 -07:00
|
|
|
const cartsDatas = await Promise.all(carts.map(cart => getRom(cart)));
|
2024-03-29 20:23:14 -07:00
|
|
|
const handle = rawRenderCart(Module, carts.map(cart => cart.name), cartsDatas, audioContext);
|
2024-03-29 02:02:40 -07:00
|
|
|
handle.pico8_state = {};
|
|
|
|
handle.pico8_buttons = [0,0,0,0,0,0,0,0];
|
|
|
|
handle.pico8_mouse = [0,0,0];
|
2024-04-04 08:58:39 -07:00
|
|
|
handle.pico8_gpio = [
|
2024-03-29 02:02:40 -07:00
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
];
|
2024-04-04 08:58:39 -07:00
|
|
|
// let gpioChanged = (gpio: number[]) => {};
|
|
|
|
// const gpioInner = [
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
|
|
// ];
|
|
|
|
// handle.pico8_gpio = new Proxy(gpioInner, {
|
|
|
|
// get(target, prop) {
|
|
|
|
// return target[prop as any];
|
|
|
|
// },
|
|
|
|
// set(target, prop, newValue) {
|
|
|
|
// const t = target as any;
|
|
|
|
// if (t.setting) {
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
// const prev = [...target];
|
|
|
|
// target[prop as any] = newValue;
|
|
|
|
// const next = [...target];
|
|
|
|
// if (!t.dontSend && prev.some((p, i) => p !== next[i])) {
|
|
|
|
// gpioChanged(target);
|
|
|
|
// }
|
|
|
|
// return true;
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// (handle as any).pico8_gpio.subscribe = (f: (gpio: number[]) => void) => {
|
|
|
|
// gpioChanged = f;
|
|
|
|
// }
|
2024-03-29 02:02:40 -07:00
|
|
|
handle.pico8_gamepads = {count: 0};
|
|
|
|
return {
|
2024-03-31 12:13:27 -07:00
|
|
|
raw: handle,
|
|
|
|
rawModule: Module,
|
2024-03-29 02:02:40 -07:00
|
|
|
canvas,
|
|
|
|
state: {
|
|
|
|
frameNumber: handle.pico8_state.frame_number!,
|
|
|
|
isPaused: !!handle.pico8_state.is_paused!,
|
|
|
|
hasFocus: !!handle.pico8_state.has_focus!,
|
|
|
|
requestPointerLock: !!handle.pico8_state.request_pointer_lock!,
|
|
|
|
requirePageNavigateConfirmation: !!handle.pico8_state.require_page_navigate_confirmation!,
|
|
|
|
showDpad: !!handle.pico8_state.show_dpad!,
|
|
|
|
shutdownRequested: !!handle.pico8_state.shutdown_requested!,
|
|
|
|
soundVolume: handle.pico8_state.sound_volume!,
|
|
|
|
},
|
2024-04-03 22:53:11 -07:00
|
|
|
gpio: handle.pico8_gpio as PicoPlayerHandle["gpio"],
|
2024-03-29 02:02:40 -07:00
|
|
|
setMouse({x, y, leftClick, rightClick}) {
|
|
|
|
handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)];
|
|
|
|
},
|
|
|
|
setButtons(buttons) {
|
|
|
|
// TODO: pad this properly here instead of casting
|
|
|
|
handle.pico8_buttons = buttons.map(({left, right, up, down, o, x, menu}) => bitfield(left, right, up, down, o, x, menu)) as any;
|
|
|
|
},
|
|
|
|
setGamepadCount(count) {
|
|
|
|
handle.pico8_gamepads = {count};
|
|
|
|
},
|
|
|
|
setTouchDetected(touchDetected) {
|
|
|
|
handle.p8_touch_detected = touchDetected ? 1 : 0;
|
|
|
|
},
|
|
|
|
dropCart(cart) {
|
|
|
|
handle.p8_dropped_cart_name = cart.name;
|
|
|
|
// TODO: make sure this is a dataURL first, and if not, load it and then pass it in
|
2024-03-29 22:23:01 -07:00
|
|
|
// handle.p8_dropped_cart = cart.src;
|
2024-03-30 15:07:49 -07:00
|
|
|
// handle.codo_command = 9;
|
2024-03-29 20:23:14 -07:00
|
|
|
},
|
|
|
|
modDragOver: (Module as any).pico8DragOver,
|
|
|
|
modDragStop: (Module as any).pico8DragStop,
|
|
|
|
togglePaused: (Module as any).pico8TogglePaused,
|
|
|
|
toggleSound: (Module as any).pico8ToggleSound,
|
|
|
|
toggleControlMenu: (Module as any).pico8ToggleControlMenu,
|
2024-03-29 02:02:40 -07:00
|
|
|
}
|
|
|
|
}
|