mirror of
https://github.com/litruv/picoGraph.git
synced 2026-07-24 02:36:04 +10:00
Initial commit
moved from private version control
This commit is contained in:
296
scripts/nodes/NodeRegistry.js
Normal file
296
scripts/nodes/NodeRegistry.js
Normal file
@@ -0,0 +1,296 @@
|
||||
import { BlueprintNode } from "../core/BlueprintNode.js";
|
||||
import { nodeModules } from "./library/index.js";
|
||||
|
||||
/** @typedef {import('./nodeTypes.js').NodeDefinition} NodeDefinition */
|
||||
/** @typedef {import('./nodeTypes.js').NodeBehavior} NodeBehavior */
|
||||
/** @typedef {import('./nodeTypes.js').NodeModule} NodeModule */
|
||||
/** @typedef {{ value: string, weight: number }} SearchField */
|
||||
|
||||
/**
|
||||
* Central registry providing node definitions, behavior hooks, and factory helpers.
|
||||
*/
|
||||
export class NodeRegistry {
|
||||
constructor() {
|
||||
/** @type {Map<string, NodeDefinition>} */
|
||||
this.definitions = new Map();
|
||||
/** @type {Map<string, NodeBehavior>} */
|
||||
this.behaviors = new Map();
|
||||
/** @type {Set<string>} */
|
||||
this.entryNodeTypes = new Set();
|
||||
/** @type {Map<string, '_init'|'_update'|'_draw'|'_update60'>} */
|
||||
this.entryPointEvents = new Map();
|
||||
|
||||
nodeModules.forEach((module) => this.registerModule(module));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes search inputs for case-insensitive comparisons.
|
||||
*
|
||||
* @param {string} query Raw query string.
|
||||
* @returns {string}
|
||||
*/
|
||||
#normalizeQuery(query) {
|
||||
return (query ?? "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a node definition matches the provided query.
|
||||
*
|
||||
* @param {NodeDefinition} definition Definition to inspect.
|
||||
* @param {string} query Search query.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
matchesDefinition(definition, query) {
|
||||
const normalized = this.#normalizeQuery(query);
|
||||
if (!normalized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const score = this.#scoreDefinition(definition, normalized);
|
||||
return Number.isFinite(score);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates every registered definition.
|
||||
*
|
||||
* @returns {Array<NodeDefinition>}
|
||||
*/
|
||||
list() {
|
||||
return [...this.definitions.values()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single node definition.
|
||||
*
|
||||
* @param {string} id Definition identifier.
|
||||
* @returns {NodeDefinition | undefined}
|
||||
*/
|
||||
get(id) {
|
||||
return this.definitions.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a fuzzy search across node metadata and returns results sorted by distance.
|
||||
*
|
||||
* @param {string} query Search input.
|
||||
* @returns {Array<NodeDefinition>}
|
||||
*/
|
||||
search(query) {
|
||||
const normalized = this.#normalizeQuery(query);
|
||||
if (!normalized) {
|
||||
return this.list().sort((a, b) => a.title.localeCompare(b.title));
|
||||
}
|
||||
|
||||
/** @type {Array<{ definition: NodeDefinition, score: number }>} */
|
||||
const ranked = [];
|
||||
this.definitions.forEach((definition) => {
|
||||
const score = this.#scoreDefinition(definition, normalized);
|
||||
if (Number.isFinite(score)) {
|
||||
ranked.push({ definition, score });
|
||||
}
|
||||
});
|
||||
|
||||
ranked.sort((a, b) => {
|
||||
if (a.score === b.score) {
|
||||
return a.definition.title.localeCompare(b.definition.title);
|
||||
}
|
||||
return a.score - b.score;
|
||||
});
|
||||
|
||||
return ranked.map((entry) => entry.definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates searchable fields for a node definition and assigns weighting.
|
||||
*
|
||||
* @param {NodeDefinition} definition Definition to inspect.
|
||||
* @returns {Array<SearchField>} Normalized field collection with weights.
|
||||
*/
|
||||
#collectSearchFields(definition) {
|
||||
const fields = /** @type {Array<SearchField>} */ ([
|
||||
{ value: definition.title, weight: 0.8 },
|
||||
{ value: definition.category, weight: 1 },
|
||||
]);
|
||||
|
||||
if (definition.description) {
|
||||
fields.push({ value: definition.description, weight: 1 });
|
||||
}
|
||||
|
||||
if (Array.isArray(definition.searchTags)) {
|
||||
definition.searchTags.forEach((tag) => {
|
||||
fields.push({ value: tag, weight: 1 });
|
||||
});
|
||||
}
|
||||
|
||||
return fields
|
||||
.filter((field) => typeof field.value === "string" && field.value.trim().length)
|
||||
.map((field) => ({
|
||||
value: field.value.toLowerCase(),
|
||||
weight: field.weight,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a fuzzy score representing how closely a definition matches a query.
|
||||
*
|
||||
* @param {NodeDefinition} definition Target definition.
|
||||
* @param {string} normalizedQuery Lowercase trimmed query string.
|
||||
* @returns {number} Matching score where lower values are closer; Infinity means no match.
|
||||
*/
|
||||
#scoreDefinition(definition, normalizedQuery) {
|
||||
const fields = this.#collectSearchFields(definition);
|
||||
if (!fields.length) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
let best = Number.POSITIVE_INFINITY;
|
||||
for (const field of fields) {
|
||||
const score = this.#computeFuzzyScore(normalizedQuery, field.value);
|
||||
if (!Number.isFinite(score)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const weighted = score * field.weight;
|
||||
if (weighted < best) {
|
||||
best = weighted;
|
||||
}
|
||||
if (best === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a fuzzy matching distance between the query and a candidate string.
|
||||
*
|
||||
* @param {string} query Normalized query string.
|
||||
* @param {string} candidate Normalized candidate string.
|
||||
* @returns {number} Distance score; Infinity indicates the query is not a subsequence of the candidate.
|
||||
*/
|
||||
#computeFuzzyScore(query, candidate) {
|
||||
if (!candidate) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
const qLength = query.length;
|
||||
const cLength = candidate.length;
|
||||
if (!qLength) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let qIndex = 0;
|
||||
let score = 0;
|
||||
let lastMatch = -1;
|
||||
|
||||
for (let cIndex = 0; cIndex < cLength; cIndex += 1) {
|
||||
if (candidate[cIndex] !== query[qIndex]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastMatch === -1) {
|
||||
score += cIndex;
|
||||
} else {
|
||||
score += cIndex - lastMatch - 1;
|
||||
}
|
||||
|
||||
lastMatch = cIndex;
|
||||
qIndex += 1;
|
||||
|
||||
if (qIndex === qLength) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (qIndex !== qLength) {
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
score += cLength - lastMatch - 1;
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates a blueprint node from a definition.
|
||||
*
|
||||
* @param {string} definitionId Definition identifier.
|
||||
* @param {{id: string, position: {x:number,y:number}}} options Initialization options.
|
||||
* @returns {BlueprintNode}
|
||||
*/
|
||||
createNode(definitionId, options) {
|
||||
const definition = this.get(definitionId);
|
||||
if (!definition) {
|
||||
throw new Error(`Unknown node definition: ${definitionId}`);
|
||||
}
|
||||
|
||||
const properties = {};
|
||||
definition.properties.forEach((schema) => {
|
||||
const value = schema.defaultValue ?? null;
|
||||
properties[schema.key] = value;
|
||||
});
|
||||
|
||||
definition.initializeProperties?.(properties);
|
||||
|
||||
return new BlueprintNode({
|
||||
id: options.id,
|
||||
type: definition.id,
|
||||
title: definition.title,
|
||||
position: { ...options.position },
|
||||
inputs: definition.inputs.map((pin) => ({
|
||||
...pin,
|
||||
})),
|
||||
outputs: definition.outputs.map((pin) => ({
|
||||
...pin,
|
||||
})),
|
||||
properties,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a node module exposing editor metadata and Lua behavior.
|
||||
*
|
||||
* @param {NodeModule} module Node module to register.
|
||||
*/
|
||||
registerModule(module) {
|
||||
const { definition, behavior } = module;
|
||||
this.definitions.set(definition.id, definition);
|
||||
|
||||
if (behavior) {
|
||||
this.behaviors.set(definition.id, behavior);
|
||||
if (behavior.isEntryPoint) {
|
||||
this.entryNodeTypes.add(definition.id);
|
||||
if (behavior.eventName) {
|
||||
this.entryPointEvents.set(definition.id, behavior.eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves behavior metadata for a node type.
|
||||
*
|
||||
* @param {string} id Node identifier.
|
||||
* @returns {NodeBehavior | undefined}
|
||||
*/
|
||||
getBehavior(id) {
|
||||
return this.behaviors.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates node types flagged as entry points.
|
||||
*
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
getEntryNodeTypes() {
|
||||
return [...this.entryNodeTypes];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes lifecycle event bindings for entry nodes.
|
||||
*
|
||||
* @returns {Map<string, '_init'|'_update'|'_draw'|'_update60'>}
|
||||
*/
|
||||
getEntryPointEvents() {
|
||||
return new Map(this.entryPointEvents);
|
||||
}
|
||||
}
|
||||
41
scripts/nodes/library/addNumber.js
Normal file
41
scripts/nodes/library/addNumber.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Adds two numeric values.
|
||||
*/
|
||||
export const addNumberNode = createNodeModule(
|
||||
{
|
||||
id: "add_number",
|
||||
title: "Add",
|
||||
category: "Math",
|
||||
description: "Add two numbers.",
|
||||
searchTags: ["add", "math", "sum", "number"],
|
||||
inputs: [
|
||||
{
|
||||
id: "a",
|
||||
name: "A",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "b",
|
||||
name: "B",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "res", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `(${a}) + (${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
16
scripts/nodes/library/audio/index.js
Normal file
16
scripts/nodes/library/audio/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { sfxNode } from "./sfx.js";
|
||||
import { musicNode } from "./music.js";
|
||||
|
||||
/**
|
||||
* All audio-related node modules backed by PICO-8 helpers.
|
||||
*/
|
||||
export const audioNodes = [sfxNode, musicNode];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented audio helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const audioFunctionChecklist = [
|
||||
{ function: "SFX", nodeId: "audio_sfx", implemented: true },
|
||||
{ function: "MUSIC", nodeId: "audio_music", implemented: true },
|
||||
];
|
||||
64
scripts/nodes/library/audio/music.js
Normal file
64
scripts/nodes/library/audio/music.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Plays or stops music using the PICO-8 music helper.
|
||||
*/
|
||||
export const musicNode = createNodeModule(
|
||||
{
|
||||
id: "audio_music",
|
||||
title: "Play Music",
|
||||
category: "Audio",
|
||||
description:
|
||||
"Start, fade, or stop music playback using MUSIC().",
|
||||
searchTags: ["music", "audio", "song", "pattern"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "pattern",
|
||||
name: "Pattern",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Pattern index or command",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "fade",
|
||||
name: "Fade (ms)",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Fade duration in milliseconds",
|
||||
},
|
||||
{
|
||||
id: "mask",
|
||||
name: "Channel Mask",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Bitmask reserving channels",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const pattern = resolveValueInput("pattern", "0");
|
||||
const fade = resolveValueInput("fade", OMIT);
|
||||
const mask = resolveValueInput("mask", OMIT);
|
||||
|
||||
const args = [pattern];
|
||||
if (fade !== OMIT || mask !== OMIT) {
|
||||
const fadeArg = fade === OMIT ? "nil" : fade;
|
||||
args.push(fadeArg);
|
||||
if (mask !== OMIT) {
|
||||
args.push(mask);
|
||||
}
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}music(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
76
scripts/nodes/library/audio/sfx.js
Normal file
76
scripts/nodes/library/audio/sfx.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Plays or controls a sound effect using the PICO-8 sfx helper.
|
||||
*/
|
||||
export const sfxNode = createNodeModule(
|
||||
{
|
||||
id: "audio_sfx",
|
||||
title: "Play SFX",
|
||||
category: "Audio",
|
||||
description:
|
||||
"Trigger a sound effect, control looping, or stop playback via SFX().",
|
||||
searchTags: ["sfx", "audio", "sound", "effect"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "id",
|
||||
name: "SFX",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Sound effect slot or command",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "channel",
|
||||
name: "Channel",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Playback channel (-1 auto)",
|
||||
},
|
||||
{
|
||||
id: "offset",
|
||||
name: "Offset",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Note offset",
|
||||
},
|
||||
{
|
||||
id: "length",
|
||||
name: "Length",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Number of notes to play",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const id = resolveValueInput("id", "0");
|
||||
const channel = resolveValueInput("channel", OMIT);
|
||||
const offset = resolveValueInput("offset", OMIT);
|
||||
const length = resolveValueInput("length", OMIT);
|
||||
|
||||
const args = [id];
|
||||
if (channel !== OMIT || offset !== OMIT || length !== OMIT) {
|
||||
const channelArg = channel === OMIT ? "nil" : channel;
|
||||
args.push(channelArg);
|
||||
if (offset !== OMIT || length !== OMIT) {
|
||||
const offsetArg = offset === OMIT ? "nil" : offset;
|
||||
args.push(offsetArg);
|
||||
if (length !== OMIT) {
|
||||
args.push(length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}sfx(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
26
scripts/nodes/library/booleanLiteral.js
Normal file
26
scripts/nodes/library/booleanLiteral.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Constant boolean literal.
|
||||
*/
|
||||
export const booleanLiteralNode = createNodeModule(
|
||||
{
|
||||
id: "boolean_literal",
|
||||
title: "Boolean",
|
||||
category: "Values",
|
||||
description: "Constant boolean literal.",
|
||||
searchTags: ["boolean", "literal", "true", "false"],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [
|
||||
{ key: "value", label: "Value", type: "boolean", defaultValue: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ node, formatLiteral }) => {
|
||||
return formatLiteral("boolean", node.properties.value ?? true);
|
||||
},
|
||||
}
|
||||
);
|
||||
32
scripts/nodes/library/callCustomEvent.js
Normal file
32
scripts/nodes/library/callCustomEvent.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Invokes a custom event by reference.
|
||||
* @type {import('../nodeTypes.js').NodeModule}
|
||||
*/
|
||||
export const callCustomEventNode = createNodeModule(
|
||||
{
|
||||
id: "call_custom_event",
|
||||
title: "Call Custom Event",
|
||||
category: "Events",
|
||||
description: "Invokes a custom event defined elsewhere in the graph.",
|
||||
searchTags: ["event", "call", "trigger", "custom"],
|
||||
inputs: [{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" }],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
initializeProperties: (properties) => {
|
||||
properties.eventId =
|
||||
typeof properties.eventId === "string" ? properties.eventId : "";
|
||||
if (
|
||||
!properties.arguments ||
|
||||
typeof properties.arguments !== "object" ||
|
||||
Array.isArray(properties.arguments)
|
||||
) {
|
||||
properties.arguments = {};
|
||||
}
|
||||
},
|
||||
},
|
||||
null
|
||||
);
|
||||
47
scripts/nodes/library/compare.js
Normal file
47
scripts/nodes/library/compare.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Compares two values using a selected operator.
|
||||
*/
|
||||
export const compareNode = createNodeModule(
|
||||
{
|
||||
id: "compare",
|
||||
title: "Compare",
|
||||
category: "Logic",
|
||||
description: "Compare two values with a selected operator.",
|
||||
searchTags: ["compare", "logic", "condition", "branch"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "any" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "any" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "res", name: "Result", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
key: "operator",
|
||||
label: "Operator",
|
||||
type: "enum",
|
||||
defaultValue: "==",
|
||||
options: [
|
||||
{ label: "Equal", value: "==" },
|
||||
{ label: "Not Equal", value: "!=" },
|
||||
{ label: "Greater", value: ">" },
|
||||
{ label: "Less", value: "<" },
|
||||
{ label: "Greater Or Equal", value: ">=" },
|
||||
{ label: "Less Or Equal", value: "<=" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ node, resolveValueInput, sanitizeOperator }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
const operator = sanitizeOperator(
|
||||
String(node.properties.operator ?? "==")
|
||||
);
|
||||
return `(${a}) ${operator} (${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
44
scripts/nodes/library/customEvent.js
Normal file
44
scripts/nodes/library/customEvent.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Defines a reusable custom event entry point.
|
||||
* @type {import('../nodeTypes.js').NodeModule}
|
||||
*/
|
||||
export const customEventNode = createNodeModule(
|
||||
{
|
||||
id: "custom_event",
|
||||
title: "Custom Event",
|
||||
category: "Events",
|
||||
description: "Defines a custom event that can be triggered elsewhere.",
|
||||
searchTags: ["event", "custom", "broadcast", "trigger"],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
key: "name",
|
||||
label: "Event Name",
|
||||
type: "string",
|
||||
defaultValue: "CustomEvent",
|
||||
placeholder: "Enter event name",
|
||||
},
|
||||
],
|
||||
initializeProperties: (properties) => {
|
||||
if (typeof properties.name !== "string" || !properties.name.trim()) {
|
||||
properties.name = "CustomEvent";
|
||||
}
|
||||
if (!Array.isArray(properties.parameters)) {
|
||||
properties.parameters = [];
|
||||
}
|
||||
const counter = Number.isFinite(properties.parameterCounter)
|
||||
? Number(properties.parameterCounter)
|
||||
: 0;
|
||||
properties.parameterCounter = counter;
|
||||
},
|
||||
},
|
||||
{
|
||||
isEntryPoint: true,
|
||||
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
|
||||
}
|
||||
);
|
||||
33
scripts/nodes/library/data/cartdata.js
Normal file
33
scripts/nodes/library/data/cartdata.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Opens persistent cartridge storage using cartdata.
|
||||
*/
|
||||
export const cartdataNode = createNodeModule(
|
||||
{
|
||||
id: "data_cartdata",
|
||||
title: "Cart Data Init",
|
||||
category: "Data",
|
||||
description: "Setup persistent storage for the cartridge using CARTDATA().",
|
||||
searchTags: ["cartdata", "save", "persistent", "data"],
|
||||
inputs: [
|
||||
{
|
||||
id: "id",
|
||||
name: "Identifier",
|
||||
direction: "input",
|
||||
kind: "string",
|
||||
defaultValue: "\"my_cart\"",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "loaded", name: "Loaded", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const id = resolveValueInput("id", '"pico_cart"');
|
||||
return `cartdata(${id})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
33
scripts/nodes/library/data/dget.js
Normal file
33
scripts/nodes/library/data/dget.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads a persistent value using dget.
|
||||
*/
|
||||
export const dgetNode = createNodeModule(
|
||||
{
|
||||
id: "data_dget",
|
||||
title: "Data Get",
|
||||
category: "Data",
|
||||
description: "Retrieve a persistent number using DGET().",
|
||||
searchTags: ["dget", "load", "data", "persistent"],
|
||||
inputs: [
|
||||
{
|
||||
id: "index",
|
||||
name: "Index",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const index = resolveValueInput("index", "0");
|
||||
return `dget(${index})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
43
scripts/nodes/library/data/dset.js
Normal file
43
scripts/nodes/library/data/dset.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Writes a persistent value using dset.
|
||||
*/
|
||||
export const dsetNode = createNodeModule(
|
||||
{
|
||||
id: "data_dset",
|
||||
title: "Data Set",
|
||||
category: "Data",
|
||||
description: "Store a persistent number using DSET().",
|
||||
searchTags: ["dset", "save", "data", "persistent"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "index",
|
||||
name: "Index",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const index = resolveValueInput("index", "0");
|
||||
const value = resolveValueInput("value", "0");
|
||||
const line = `${indent(indentLevel)}dset(${index}, ${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
18
scripts/nodes/library/data/index.js
Normal file
18
scripts/nodes/library/data/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { cartdataNode } from "./cartdata.js";
|
||||
import { dsetNode } from "./dset.js";
|
||||
import { dgetNode } from "./dget.js";
|
||||
|
||||
/**
|
||||
* All data persistence node modules backed by PICO-8 helpers.
|
||||
*/
|
||||
export const dataNodes = [cartdataNode, dsetNode, dgetNode];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented cart data helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const dataFunctionChecklist = [
|
||||
{ function: "CARTDATA", nodeId: "data_cartdata", implemented: true },
|
||||
{ function: "DSET", nodeId: "data_dset", implemented: true },
|
||||
{ function: "DGET", nodeId: "data_dget", implemented: true },
|
||||
];
|
||||
35
scripts/nodes/library/devkit/devkitConfig.js
Normal file
35
scripts/nodes/library/devkit/devkitConfig.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Applies host configuration flags via devkit_config().
|
||||
*/
|
||||
export const devkitConfigNode = createNodeModule(
|
||||
{
|
||||
id: "devkit_config",
|
||||
title: "Devkit Config",
|
||||
category: "Devkit",
|
||||
description: "Send a configuration string to devkit_config().",
|
||||
searchTags: ["devkit", "config", "host"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "config",
|
||||
name: "Config",
|
||||
direction: "input",
|
||||
kind: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const config = resolveValueInput("config", '""');
|
||||
const line = `${indent(indentLevel)}devkit_config(${config})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
33
scripts/nodes/library/devkit/devkitInputRead.js
Normal file
33
scripts/nodes/library/devkit/devkitInputRead.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads values from devkit-provided inputs via devkit_input().
|
||||
*/
|
||||
export const devkitInputReadNode = createNodeModule(
|
||||
{
|
||||
id: "devkit_input_read",
|
||||
title: "Devkit Input Read",
|
||||
category: "Devkit",
|
||||
description: "Fetch the value of a devkit input index.",
|
||||
searchTags: ["devkit", "input", "read"],
|
||||
inputs: [
|
||||
{
|
||||
id: "index",
|
||||
name: "Index",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "any" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const index = resolveValueInput("index", "0");
|
||||
return `devkit_input(${index})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
43
scripts/nodes/library/devkit/devkitInputWrite.js
Normal file
43
scripts/nodes/library/devkit/devkitInputWrite.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Sends values to devkit inputs via devkit_input().
|
||||
*/
|
||||
export const devkitInputWriteNode = createNodeModule(
|
||||
{
|
||||
id: "devkit_input_write",
|
||||
title: "Devkit Input Write",
|
||||
category: "Devkit",
|
||||
description: "Push a value to a devkit input index.",
|
||||
searchTags: ["devkit", "input", "write"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "index",
|
||||
name: "Index",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const index = resolveValueInput("index", "0");
|
||||
const value = resolveValueInput("value", "0");
|
||||
const line = `${indent(indentLevel)}devkit_input(${index}, ${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
22
scripts/nodes/library/devkit/index.js
Normal file
22
scripts/nodes/library/devkit/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { devkitConfigNode } from "./devkitConfig.js";
|
||||
import { devkitInputReadNode } from "./devkitInputRead.js";
|
||||
import { devkitInputWriteNode } from "./devkitInputWrite.js";
|
||||
|
||||
/**
|
||||
* Node modules covering devkit helpers.
|
||||
*/
|
||||
export const devkitNodes = [
|
||||
devkitConfigNode,
|
||||
devkitInputReadNode,
|
||||
devkitInputWriteNode,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of devkit helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const devkitFunctionChecklist = [
|
||||
{ function: "DEVKIT_CONFIG", nodeId: "devkit_config", implemented: true },
|
||||
{ function: "DEVKIT_INPUT (read)", nodeId: "devkit_input_read", implemented: true },
|
||||
{ function: "DEVKIT_INPUT (write)", nodeId: "devkit_input_write", implemented: true },
|
||||
];
|
||||
26
scripts/nodes/library/eventDraw.js
Normal file
26
scripts/nodes/library/eventDraw.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Lifecycle event triggered once per visible frame.
|
||||
* @type {import('../nodeTypes.js').NodeModule}
|
||||
*/
|
||||
export const eventDrawNode = createNodeModule(
|
||||
{
|
||||
id: "event_draw",
|
||||
title: "Event Draw",
|
||||
category: "Events",
|
||||
description: "Called once per visible frame.",
|
||||
searchTags: ["draw", "render", "frame", "loop"],
|
||||
unique: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
isEntryPoint: true,
|
||||
eventName: "_draw",
|
||||
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
|
||||
}
|
||||
);
|
||||
26
scripts/nodes/library/eventStart.js
Normal file
26
scripts/nodes/library/eventStart.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Lifecycle entry triggered once when the cart boots.
|
||||
* @type {import('../nodeTypes.js').NodeModule}
|
||||
*/
|
||||
export const eventInitNode = createNodeModule(
|
||||
{
|
||||
id: "event_start",
|
||||
title: "Event Init",
|
||||
category: "Events",
|
||||
description: "Called once on cart startup.",
|
||||
searchTags: ["start", "init", "boot", "lifecycle"],
|
||||
unique: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
isEntryPoint: true,
|
||||
eventName: "_init",
|
||||
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/eventUpdate.js
Normal file
25
scripts/nodes/library/eventUpdate.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* @type {import('../nodeTypes.js').NodeModule}
|
||||
*/
|
||||
export const eventUpdateNode = createNodeModule(
|
||||
{
|
||||
id: "event_update",
|
||||
title: "Event Update",
|
||||
category: "Events",
|
||||
description: "Called once per update",
|
||||
searchTags: ["update", "loop", "frame", "tick"],
|
||||
unique: true,
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
isEntryPoint: true,
|
||||
eventName: "_update",
|
||||
emitExec: ({ emitNextExec }) => emitNextExec("exec_out"),
|
||||
}
|
||||
);
|
||||
92
scripts/nodes/library/forLoop.js
Normal file
92
scripts/nodes/library/forLoop.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Iterates from start to end inclusive using PICO-8 for semantics.
|
||||
*/
|
||||
export const forLoopNode = createNodeModule(
|
||||
{
|
||||
id: "for_loop",
|
||||
title: "For Loop",
|
||||
category: "Logic",
|
||||
description:
|
||||
"Iterate from start to end inclusive using PICO-8 for semantics.",
|
||||
searchTags: ["loop", "for", "iterate", "counter"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "start",
|
||||
name: "Start",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "end",
|
||||
name: "End",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 10,
|
||||
},
|
||||
{
|
||||
id: "step",
|
||||
name: "Step",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 1,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "loop", name: "Loop", direction: "output", kind: "exec" },
|
||||
{ id: "completed", name: "Completed", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
key: "index",
|
||||
label: "Index Variable",
|
||||
type: "string",
|
||||
defaultValue: "i",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
emitExec: ({
|
||||
node,
|
||||
indent,
|
||||
indentLevel,
|
||||
resolveValueInput,
|
||||
sanitizeIdentifier,
|
||||
emitBranch,
|
||||
path,
|
||||
}) => {
|
||||
const index = sanitizeIdentifier(String(node.properties.index ?? "i"));
|
||||
const startValue = resolveValueInput("start", "0");
|
||||
const endValue = resolveValueInput("end", "0");
|
||||
const stepValue = resolveValueInput("step", "1");
|
||||
|
||||
const lines = [
|
||||
`${indent(
|
||||
indentLevel
|
||||
)}for ${index} = ${startValue}, ${endValue}, ${stepValue} do`,
|
||||
];
|
||||
|
||||
const loopLines = emitBranch("loop", {
|
||||
indentLevel: indentLevel + 1,
|
||||
path: new Set(path),
|
||||
});
|
||||
if (!loopLines.length) {
|
||||
lines.push(`${indent(indentLevel + 1)}-- loop body`);
|
||||
} else {
|
||||
lines.push(...loopLines);
|
||||
}
|
||||
|
||||
lines.push(`${indent(indentLevel)}end`);
|
||||
lines.push(
|
||||
...emitBranch("completed", {
|
||||
indentLevel,
|
||||
path,
|
||||
})
|
||||
);
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
0
scripts/nodes/library/getLocalVariable.js
Normal file
0
scripts/nodes/library/getLocalVariable.js
Normal file
30
scripts/nodes/library/getVariable.js
Normal file
30
scripts/nodes/library/getVariable.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Exposes the current value of a variable.
|
||||
*/
|
||||
export const getVariableNode = createNodeModule(
|
||||
{
|
||||
id: "get_var",
|
||||
title: "Get Variable",
|
||||
category: "Logic",
|
||||
description: "Expose the current value of a variable.",
|
||||
searchTags: ["get", "read", "variable", "access"],
|
||||
inputs: [],
|
||||
outputs: [{ id: "value", name: "Value", direction: "output", kind: "any" }],
|
||||
properties: [
|
||||
{
|
||||
key: "name",
|
||||
label: "Variable Name",
|
||||
type: "string",
|
||||
defaultValue: "score",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ node, sanitizeIdentifier }) => {
|
||||
const name = sanitizeIdentifier(String(node.properties.name ?? "var"));
|
||||
return name;
|
||||
},
|
||||
}
|
||||
);
|
||||
33
scripts/nodes/library/gpio/gpioRead.js
Normal file
33
scripts/nodes/library/gpio/gpioRead.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads a value from a GPIO pin using gpio().
|
||||
*/
|
||||
export const gpioReadNode = createNodeModule(
|
||||
{
|
||||
id: "gpio_read",
|
||||
title: "GPIO Read",
|
||||
category: "IO",
|
||||
description: "Fetch the current value of a GPIO pin.",
|
||||
searchTags: ["gpio", "hardware", "input"],
|
||||
inputs: [
|
||||
{
|
||||
id: "index",
|
||||
name: "Pin",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const pin = resolveValueInput("index", "0");
|
||||
return `gpio(${pin})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
43
scripts/nodes/library/gpio/gpioWrite.js
Normal file
43
scripts/nodes/library/gpio/gpioWrite.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Writes a value to a GPIO pin using gpio().
|
||||
*/
|
||||
export const gpioWriteNode = createNodeModule(
|
||||
{
|
||||
id: "gpio_write",
|
||||
title: "GPIO Write",
|
||||
category: "IO",
|
||||
description: "Set the value for a GPIO pin.",
|
||||
searchTags: ["gpio", "hardware", "output"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "index",
|
||||
name: "Pin",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const pin = resolveValueInput("index", "0");
|
||||
const value = resolveValueInput("value", "0");
|
||||
const line = `${indent(indentLevel)}gpio(${pin}, ${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
16
scripts/nodes/library/gpio/index.js
Normal file
16
scripts/nodes/library/gpio/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { gpioReadNode } from "./gpioRead.js";
|
||||
import { gpioWriteNode } from "./gpioWrite.js";
|
||||
|
||||
/**
|
||||
* Node modules covering GPIO helpers.
|
||||
*/
|
||||
export const gpioNodes = [gpioReadNode, gpioWriteNode];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of GPIO read/write helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const gpioFunctionChecklist = [
|
||||
{ function: "GPIO (read)", nodeId: "gpio_read", implemented: true },
|
||||
{ function: "GPIO (write)", nodeId: "gpio_write", implemented: true },
|
||||
];
|
||||
37
scripts/nodes/library/graphics/camera.js
Normal file
37
scripts/nodes/library/graphics/camera.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Applies a camera offset to subsequent draw calls.
|
||||
*/
|
||||
export const cameraNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_camera",
|
||||
title: "Add Camera Offset",
|
||||
category: "Graphics",
|
||||
description: "Shift the draw origin by the specified camera offset.",
|
||||
searchTags: ["camera", "offset", "scroll", "view"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "x", name: "X", direction: "input", kind: "number" },
|
||||
{ id: "y", name: "Y", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const x = resolveValueInput("x", OMIT);
|
||||
const y = resolveValueInput("y", OMIT);
|
||||
let call = "camera()";
|
||||
if (x !== OMIT) {
|
||||
call = `camera(${x}, ${y === OMIT ? "0" : y})`;
|
||||
}
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
57
scripts/nodes/library/graphics/circ.js
Normal file
57
scripts/nodes/library/graphics/circ.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws an outlined circle.
|
||||
*/
|
||||
export const circNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_circ",
|
||||
title: "Draw Circle",
|
||||
category: "Graphics",
|
||||
description: "Render an outlined circle at the specified position.",
|
||||
searchTags: ["circle", "outline", "shape", "draw"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "radius",
|
||||
name: "Radius",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 4,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const radius = resolveValueInput("radius", "4");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x, y, radius];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}circ(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
57
scripts/nodes/library/graphics/circfill.js
Normal file
57
scripts/nodes/library/graphics/circfill.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a filled circle.
|
||||
*/
|
||||
export const circfillNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_circfill",
|
||||
title: "Draw Filled Circle",
|
||||
category: "Graphics",
|
||||
description: "Render a filled circle at the specified position.",
|
||||
searchTags: ["circle", "filled", "shape", "draw"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "radius",
|
||||
name: "Radius",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 4,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const radius = resolveValueInput("radius", "4");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x, y, radius];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}circfill(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
63
scripts/nodes/library/graphics/clip.js
Normal file
63
scripts/nodes/library/graphics/clip.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Adjusts the active clipping rectangle for subsequent draw calls.
|
||||
*/
|
||||
export const clipNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_clip",
|
||||
title: "Set Clipping Rectangle",
|
||||
category: "Graphics",
|
||||
description:
|
||||
"Set or reset the clipping rectangle that constrains all draw operations.",
|
||||
searchTags: ["clip", "clipping", "graphics", "viewport"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "x", name: "X", direction: "input", kind: "number" },
|
||||
{ id: "y", name: "Y", direction: "input", kind: "number" },
|
||||
{ id: "w", name: "Width", direction: "input", kind: "number" },
|
||||
{ id: "h", name: "Height", direction: "input", kind: "number" },
|
||||
{
|
||||
id: "clip_previous",
|
||||
name: "Clip Previous",
|
||||
direction: "input",
|
||||
kind: "boolean",
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const x = resolveValueInput("x", OMIT);
|
||||
const y = resolveValueInput("y", OMIT);
|
||||
const w = resolveValueInput("w", OMIT);
|
||||
const h = resolveValueInput("h", OMIT);
|
||||
const clipPrevious = resolveValueInput("clip_previous", OMIT);
|
||||
|
||||
let call;
|
||||
if (x === OMIT && y === OMIT && w === OMIT && h === OMIT) {
|
||||
call = "clip()";
|
||||
} else {
|
||||
const args = [
|
||||
x === OMIT ? "0" : x,
|
||||
y === OMIT ? "0" : y,
|
||||
w === OMIT ? "128" : w,
|
||||
h === OMIT ? "128" : h,
|
||||
];
|
||||
if (clipPrevious !== OMIT) {
|
||||
args.push(clipPrevious);
|
||||
}
|
||||
call = `clip(${args.join(", ")})`;
|
||||
}
|
||||
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
32
scripts/nodes/library/graphics/cls.js
Normal file
32
scripts/nodes/library/graphics/cls.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Clears the screen and resets clipping.
|
||||
*/
|
||||
export const clsNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_cls",
|
||||
title: "Clear Screen",
|
||||
category: "Graphics",
|
||||
description: "Clear the display, optionally filling with a specific color.",
|
||||
searchTags: ["cls", "clear", "screen", "background"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
const call = color === OMIT ? "cls()" : `cls(${color})`;
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
32
scripts/nodes/library/graphics/color.js
Normal file
32
scripts/nodes/library/graphics/color.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Updates the current draw color.
|
||||
*/
|
||||
export const colorNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_color",
|
||||
title: "Set Active Color",
|
||||
category: "Graphics",
|
||||
description: "Set the active draw color used by subsequent graphics calls.",
|
||||
searchTags: ["color", "color", "ink", "draw"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
const call = color === OMIT ? "color()" : `color(${color})`;
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
46
scripts/nodes/library/graphics/cursor.js
Normal file
46
scripts/nodes/library/graphics/cursor.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Positions the print cursor and optionally sets the draw color.
|
||||
*/
|
||||
export const cursorNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_cursor",
|
||||
title: "Set Cursor",
|
||||
category: "Graphics",
|
||||
description:
|
||||
"Move the print cursor and optionally change the active color.",
|
||||
searchTags: ["cursor", "print", "text", "position"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "x", name: "X", direction: "input", kind: "number" },
|
||||
{ id: "y", name: "Y", direction: "input", kind: "number" },
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const x = resolveValueInput("x", OMIT);
|
||||
const y = resolveValueInput("y", OMIT);
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
|
||||
let call = "cursor()";
|
||||
if (x !== OMIT) {
|
||||
const args = [x, y === OMIT ? "0" : y];
|
||||
if (color !== OMIT) {
|
||||
args.push(color);
|
||||
}
|
||||
call = `cursor(${args.join(", ")})`;
|
||||
}
|
||||
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
39
scripts/nodes/library/graphics/fget.js
Normal file
39
scripts/nodes/library/graphics/fget.js
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads sprite flag data.
|
||||
*/
|
||||
export const fgetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_fget",
|
||||
title: "Get Sprite Flag",
|
||||
category: "Graphics",
|
||||
description: "Retrieve the bitfield or specific flag value for a sprite.",
|
||||
searchTags: ["fget", "flag", "sprite", "meta"],
|
||||
inputs: [
|
||||
{
|
||||
id: "sprite",
|
||||
name: "Sprite",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "flag", name: "Flag", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const sprite = resolveValueInput("sprite", "0");
|
||||
const flag = resolveValueInput("flag", OMIT);
|
||||
if (flag === OMIT) {
|
||||
return `fget(${sprite})`;
|
||||
}
|
||||
return `fget(${sprite}, ${flag})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
31
scripts/nodes/library/graphics/fillp.js
Normal file
31
scripts/nodes/library/graphics/fillp.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Configures the active fill pattern.
|
||||
*/
|
||||
export const fillpNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_fillp",
|
||||
title: "Set Fill Pattern",
|
||||
category: "Graphics",
|
||||
description: "Set the 4x4 fill pattern bitfield used by many draw calls.",
|
||||
searchTags: ["fillp", "pattern", "fill", "hatch"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "pattern", name: "Pattern", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const pattern = resolveValueInput("pattern", "__pg_omit__");
|
||||
const call = pattern === "__pg_omit__" ? "fillp()" : `fillp(${pattern})`;
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
52
scripts/nodes/library/graphics/fset.js
Normal file
52
scripts/nodes/library/graphics/fset.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Updates sprite flag values.
|
||||
*/
|
||||
export const fsetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_fset",
|
||||
title: "Set Sprite Flag",
|
||||
category: "Graphics",
|
||||
description: "Set a sprite's flag bitfield or a specific flag value.",
|
||||
searchTags: ["fset", "flag", "sprite", "meta"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "sprite",
|
||||
name: "Sprite",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "flag", name: "Flag", direction: "input", kind: "number" },
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const sprite = resolveValueInput("sprite", "0");
|
||||
const flag = resolveValueInput("flag", OMIT);
|
||||
const value = resolveValueInput("value", "false");
|
||||
const args = [sprite];
|
||||
if (flag === OMIT) {
|
||||
args.push(value);
|
||||
} else {
|
||||
args.push(flag, value);
|
||||
}
|
||||
const line = `${indent(indentLevel)}fset(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
88
scripts/nodes/library/graphics/index.js
Normal file
88
scripts/nodes/library/graphics/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { clipNode } from "./clip.js";
|
||||
import { psetNode } from "./pset.js";
|
||||
import { pgetNode } from "./pget.js";
|
||||
import { sgetNode } from "./sget.js";
|
||||
import { ssetNode } from "./sset.js";
|
||||
import { fgetNode } from "./fget.js";
|
||||
import { fsetNode } from "./fset.js";
|
||||
import { cursorNode } from "./cursor.js";
|
||||
import { colorNode } from "./color.js";
|
||||
import { clsNode } from "./cls.js";
|
||||
import { cameraNode } from "./camera.js";
|
||||
import { circNode } from "./circ.js";
|
||||
import { circfillNode } from "./circfill.js";
|
||||
import { ovalNode } from "./oval.js";
|
||||
import { ovalfillNode } from "./ovalfill.js";
|
||||
import { lineNode } from "./line.js";
|
||||
import { rectNode } from "./rect.js";
|
||||
import { rectfillNode } from "./rectfill.js";
|
||||
import { rrectNode } from "./rrect.js";
|
||||
import { rrectfillNode } from "./rrectfill.js";
|
||||
import { palNode } from "./pal.js";
|
||||
import { paltNode } from "./palt.js";
|
||||
import { sprNode } from "./spr.js";
|
||||
import { ssprNode } from "./sspr.js";
|
||||
import { fillpNode } from "./fillp.js";
|
||||
|
||||
/**
|
||||
* All graphics related node modules backed by PICO-8 draw helpers.
|
||||
*/
|
||||
export const graphicsNodes = [
|
||||
clipNode,
|
||||
psetNode,
|
||||
pgetNode,
|
||||
sgetNode,
|
||||
ssetNode,
|
||||
fgetNode,
|
||||
fsetNode,
|
||||
cursorNode,
|
||||
colorNode,
|
||||
clsNode,
|
||||
cameraNode,
|
||||
circNode,
|
||||
circfillNode,
|
||||
ovalNode,
|
||||
ovalfillNode,
|
||||
lineNode,
|
||||
rectNode,
|
||||
rectfillNode,
|
||||
rrectNode,
|
||||
rrectfillNode,
|
||||
palNode,
|
||||
paltNode,
|
||||
sprNode,
|
||||
ssprNode,
|
||||
fillpNode,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented graphics helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const graphicsFunctionChecklist = [
|
||||
{ function: "CLIP", nodeId: "graphics_clip", implemented: true },
|
||||
{ function: "PSET", nodeId: "graphics_pset", implemented: true },
|
||||
{ function: "PGET", nodeId: "graphics_pget", implemented: true },
|
||||
{ function: "SGET", nodeId: "graphics_sget", implemented: true },
|
||||
{ function: "SSET", nodeId: "graphics_sset", implemented: true },
|
||||
{ function: "FGET", nodeId: "graphics_fget", implemented: true },
|
||||
{ function: "FSET", nodeId: "graphics_fset", implemented: true },
|
||||
{ function: "CURSOR", nodeId: "graphics_cursor", implemented: true },
|
||||
{ function: "COLOR", nodeId: "graphics_color", implemented: true },
|
||||
{ function: "CLS", nodeId: "graphics_cls", implemented: true },
|
||||
{ function: "CAMERA", nodeId: "graphics_camera", implemented: true },
|
||||
{ function: "CIRC", nodeId: "graphics_circ", implemented: true },
|
||||
{ function: "CIRCFILL", nodeId: "graphics_circfill", implemented: true },
|
||||
{ function: "OVAL", nodeId: "graphics_oval", implemented: true },
|
||||
{ function: "OVALFILL", nodeId: "graphics_ovalfill", implemented: true },
|
||||
{ function: "LINE", nodeId: "graphics_line", implemented: true },
|
||||
{ function: "RECT", nodeId: "graphics_rect", implemented: true },
|
||||
{ function: "RECTFILL", nodeId: "graphics_rectfill", implemented: true },
|
||||
{ function: "RRECT", nodeId: "graphics_rrect", implemented: true },
|
||||
{ function: "RRECTFILL", nodeId: "graphics_rrectfill", implemented: true },
|
||||
{ function: "PAL", nodeId: "graphics_pal", implemented: true },
|
||||
{ function: "PALT", nodeId: "graphics_palt", implemented: true },
|
||||
{ function: "SPR", nodeId: "graphics_spr", implemented: true },
|
||||
{ function: "SSPR", nodeId: "graphics_sspr", implemented: true },
|
||||
{ function: "FILLP", nodeId: "graphics_fillp", implemented: true },
|
||||
];
|
||||
60
scripts/nodes/library/graphics/line.js
Normal file
60
scripts/nodes/library/graphics/line.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a line segment.
|
||||
*/
|
||||
export const lineNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_line",
|
||||
title: "Draw Line",
|
||||
category: "Graphics",
|
||||
description:
|
||||
"Draw a line between two points or update the current pen position.",
|
||||
searchTags: ["line", "draw", "segment", "stroke"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "x1", name: "X1", direction: "input", kind: "number" },
|
||||
{ id: "y1", name: "Y1", direction: "input", kind: "number" },
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const x0 = resolveValueInput("x0", "0");
|
||||
const y0 = resolveValueInput("y0", "0");
|
||||
const x1 = resolveValueInput("x1", OMIT);
|
||||
const y1 = resolveValueInput("y1", OMIT);
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
|
||||
const args = [x0, y0];
|
||||
if (x1 !== OMIT && y1 !== OMIT) {
|
||||
args.push(x1, y1);
|
||||
if (color !== OMIT) {
|
||||
args.push(color);
|
||||
}
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}line(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
65
scripts/nodes/library/graphics/oval.js
Normal file
65
scripts/nodes/library/graphics/oval.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws an outlined oval.
|
||||
*/
|
||||
export const ovalNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_oval",
|
||||
title: "Draw Oval",
|
||||
category: "Graphics",
|
||||
description: "Render an ellipse using the provided bounding box.",
|
||||
searchTags: ["oval", "ellipse", "shape", "outline"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "x1",
|
||||
name: "X1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "y1",
|
||||
name: "Y1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x0 = resolveValueInput("x0", "0");
|
||||
const y0 = resolveValueInput("y0", "0");
|
||||
const x1 = resolveValueInput("x1", "8");
|
||||
const y1 = resolveValueInput("y1", "8");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x0, y0, x1, y1];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}oval(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
65
scripts/nodes/library/graphics/ovalfill.js
Normal file
65
scripts/nodes/library/graphics/ovalfill.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a filled oval.
|
||||
*/
|
||||
export const ovalfillNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_ovalfill",
|
||||
title: "Draw Filled Oval",
|
||||
category: "Graphics",
|
||||
description: "Render a filled ellipse using the provided bounding box.",
|
||||
searchTags: ["oval", "ellipse", "filled", "shape"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "x1",
|
||||
name: "X1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "y1",
|
||||
name: "Y1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x0 = resolveValueInput("x0", "0");
|
||||
const y0 = resolveValueInput("y0", "0");
|
||||
const x1 = resolveValueInput("x1", "8");
|
||||
const y1 = resolveValueInput("y1", "8");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x0, y0, x1, y1];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}ovalfill(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
51
scripts/nodes/library/graphics/pal.js
Normal file
51
scripts/nodes/library/graphics/pal.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Remaps palette colors.
|
||||
*/
|
||||
export const palNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_pal",
|
||||
title: "Remap Palette Color",
|
||||
category: "Graphics",
|
||||
description: "Swap one palette color for another or reset the palette.",
|
||||
searchTags: ["pal", "palette", "color", "remap"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "c0", name: "Color 0", direction: "input", kind: "number" },
|
||||
{
|
||||
id: "c1",
|
||||
name: "Color 1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "p", name: "Palette", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const c0 = resolveValueInput("c0", OMIT);
|
||||
const c1 = resolveValueInput("c1", "0");
|
||||
const p = resolveValueInput("p", OMIT);
|
||||
|
||||
let call = "pal()";
|
||||
if (c0 !== OMIT) {
|
||||
const args = [c0, c1];
|
||||
if (p !== OMIT) {
|
||||
args.push(p);
|
||||
}
|
||||
call = `pal(${args.join(", ")})`;
|
||||
}
|
||||
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
47
scripts/nodes/library/graphics/palt.js
Normal file
47
scripts/nodes/library/graphics/palt.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Adjusts sprite transparency settings.
|
||||
*/
|
||||
export const paltNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_palt",
|
||||
title: "Set Sprite Transparency",
|
||||
category: "Graphics",
|
||||
description: "Set or reset sprite transparency flags.",
|
||||
searchTags: ["palt", "transparency", "palette", "sprite"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
{
|
||||
id: "transparent",
|
||||
name: "Transparent",
|
||||
direction: "input",
|
||||
kind: "boolean",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
const transparent = resolveValueInput("transparent", OMIT);
|
||||
|
||||
let call = "palt()";
|
||||
if (color !== OMIT) {
|
||||
if (transparent === OMIT) {
|
||||
call = `palt(${color})`;
|
||||
} else {
|
||||
call = `palt(${color}, ${transparent})`;
|
||||
}
|
||||
}
|
||||
const lines = [`${indent(indentLevel)}${call}`];
|
||||
lines.push(...emitNextExec("exec_out"));
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
41
scripts/nodes/library/graphics/pget.js
Normal file
41
scripts/nodes/library/graphics/pget.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads the color of a screen pixel.
|
||||
*/
|
||||
export const pgetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_pget",
|
||||
title: "Get Pixel Color",
|
||||
category: "Graphics",
|
||||
description: "Fetch the color index at the given screen coordinate.",
|
||||
searchTags: ["pget", "pixel", "read", "sample"],
|
||||
inputs: [
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Color", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
return `pget(${x}, ${y})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
50
scripts/nodes/library/graphics/pset.js
Normal file
50
scripts/nodes/library/graphics/pset.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a single pixel using the current or specified color.
|
||||
*/
|
||||
export const psetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_pset",
|
||||
title: "Set Pixel Color",
|
||||
category: "Graphics",
|
||||
description: "Set a pixel on screen to a color index.",
|
||||
searchTags: ["pset", "pixel", "draw", "plot"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const color = resolveValueInput("color", OMIT);
|
||||
const args = [x, y];
|
||||
if (color !== OMIT) {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}pset(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
65
scripts/nodes/library/graphics/rect.js
Normal file
65
scripts/nodes/library/graphics/rect.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws an outlined rectangle.
|
||||
*/
|
||||
export const rectNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_rect",
|
||||
title: "Draw Rectangle",
|
||||
category: "Graphics",
|
||||
description: "Render a rectangle outline defined by two corners.",
|
||||
searchTags: ["rect", "rectangle", "outline", "shape"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "x1",
|
||||
name: "X1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "y1",
|
||||
name: "Y1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x0 = resolveValueInput("x0", "0");
|
||||
const y0 = resolveValueInput("y0", "0");
|
||||
const x1 = resolveValueInput("x1", "8");
|
||||
const y1 = resolveValueInput("y1", "8");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x0, y0, x1, y1];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}rect(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
65
scripts/nodes/library/graphics/rectfill.js
Normal file
65
scripts/nodes/library/graphics/rectfill.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a filled rectangle.
|
||||
*/
|
||||
export const rectfillNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_rectfill",
|
||||
title: "Draw Filled Rectangle",
|
||||
category: "Graphics",
|
||||
description: "Render a filled rectangle defined by two corners.",
|
||||
searchTags: ["rect", "rectangle", "filled", "shape"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "x1",
|
||||
name: "X1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "y1",
|
||||
name: "Y1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x0 = resolveValueInput("x0", "0");
|
||||
const y0 = resolveValueInput("y0", "0");
|
||||
const x1 = resolveValueInput("x1", "8");
|
||||
const y1 = resolveValueInput("y1", "8");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x0, y0, x1, y1];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}rectfill(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
73
scripts/nodes/library/graphics/rrect.js
Normal file
73
scripts/nodes/library/graphics/rrect.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws an outlined rounded rectangle.
|
||||
*/
|
||||
export const rrectNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_rrect",
|
||||
title: "Draw Rounded Rectangle",
|
||||
category: "Graphics",
|
||||
description: "Render a rounded rectangle outline with the supplied radius.",
|
||||
searchTags: ["rounded", "rectangle", "outline", "shape"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "w",
|
||||
name: "Width",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "h",
|
||||
name: "Height",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "r",
|
||||
name: "Radius",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 2,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const w = resolveValueInput("w", "8");
|
||||
const h = resolveValueInput("h", "8");
|
||||
const r = resolveValueInput("r", "2");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x, y, w, h, r];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}rrect(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
73
scripts/nodes/library/graphics/rrectfill.js
Normal file
73
scripts/nodes/library/graphics/rrectfill.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a filled rounded rectangle.
|
||||
*/
|
||||
export const rrectfillNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_rrectfill",
|
||||
title: "Draw Filled Rounded Rectangle",
|
||||
category: "Graphics",
|
||||
description: "Render a filled rounded rectangle with the supplied radius.",
|
||||
searchTags: ["rounded", "rectangle", "filled", "shape"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "w",
|
||||
name: "Width",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "h",
|
||||
name: "Height",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "r",
|
||||
name: "Radius",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 2,
|
||||
},
|
||||
{ id: "color", name: "Color", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const w = resolveValueInput("w", "8");
|
||||
const h = resolveValueInput("h", "8");
|
||||
const r = resolveValueInput("r", "2");
|
||||
const color = resolveValueInput("color", "__pg_omit__");
|
||||
const args = [x, y, w, h, r];
|
||||
if (color !== "__pg_omit__") {
|
||||
args.push(color);
|
||||
}
|
||||
const line = `${indent(indentLevel)}rrectfill(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
42
scripts/nodes/library/graphics/sget.js
Normal file
42
scripts/nodes/library/graphics/sget.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads a pixel from the sprite sheet.
|
||||
*/
|
||||
export const sgetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_sget",
|
||||
title: "Get Sprite Pixel",
|
||||
category: "Graphics",
|
||||
description:
|
||||
"Retrieve the color index from the sprite sheet at the given coordinate.",
|
||||
searchTags: ["sget", "sprite", "sheet", "read"],
|
||||
inputs: [
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Color", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
return `sget(${x}, ${y})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
76
scripts/nodes/library/graphics/spr.js
Normal file
76
scripts/nodes/library/graphics/spr.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws sprites from the sprite sheet.
|
||||
*/
|
||||
export const sprNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_spr",
|
||||
title: "Draw Sprite",
|
||||
category: "Graphics",
|
||||
description: "Blit one or more sprites at the given screen position.",
|
||||
searchTags: ["spr", "sprite", "draw", "blit"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "sprite",
|
||||
name: "Sprite",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "w", name: "Width", direction: "input", kind: "number" },
|
||||
{ id: "h", name: "Height", direction: "input", kind: "number" },
|
||||
{ id: "flip_x", name: "Flip X", direction: "input", kind: "boolean" },
|
||||
{ id: "flip_y", name: "Flip Y", direction: "input", kind: "boolean" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const sprite = resolveValueInput("sprite", "0");
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const w = resolveValueInput("w", OMIT);
|
||||
const h = resolveValueInput("h", OMIT);
|
||||
const flipX = resolveValueInput("flip_x", OMIT);
|
||||
const flipY = resolveValueInput("flip_y", OMIT);
|
||||
|
||||
const args = [sprite, x, y];
|
||||
const includeSize =
|
||||
w !== OMIT || h !== OMIT || flipX !== OMIT || flipY !== OMIT;
|
||||
|
||||
if (includeSize) {
|
||||
args.push(w === OMIT ? "1" : w, h === OMIT ? "1" : h);
|
||||
}
|
||||
|
||||
if (flipX !== OMIT || flipY !== OMIT) {
|
||||
args.push(
|
||||
flipX === OMIT ? "false" : flipX,
|
||||
flipY === OMIT ? "false" : flipY
|
||||
);
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}spr(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
51
scripts/nodes/library/graphics/sset.js
Normal file
51
scripts/nodes/library/graphics/sset.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Writes a pixel to the sprite sheet.
|
||||
*/
|
||||
export const ssetNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_sset",
|
||||
title: "Set Sprite Pixel",
|
||||
category: "Graphics",
|
||||
description: "Assign a color to the sprite sheet at the given coordinate.",
|
||||
searchTags: ["sset", "sprite", "sheet", "write"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "color",
|
||||
name: "Color",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const color = resolveValueInput("color", "0");
|
||||
const line = `${indent(indentLevel)}sset(${x}, ${y}, ${color})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
100
scripts/nodes/library/graphics/sspr.js
Normal file
100
scripts/nodes/library/graphics/sspr.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws a rectangular region of the sprite sheet with scaling support.
|
||||
*/
|
||||
export const ssprNode = createNodeModule(
|
||||
{
|
||||
id: "graphics_sspr",
|
||||
title: "Draw Scaled Sprite",
|
||||
category: "Graphics",
|
||||
description: "Stretch or flip a sprite sheet region to the screen.",
|
||||
searchTags: ["sspr", "sprite", "scale", "blit"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "sx",
|
||||
name: "SX",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "sy",
|
||||
name: "SY",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "sw",
|
||||
name: "SW",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "sh",
|
||||
name: "SH",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 8,
|
||||
},
|
||||
{
|
||||
id: "dx",
|
||||
name: "DX",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "dy",
|
||||
name: "DY",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{ id: "dw", name: "DW", direction: "input", kind: "number" },
|
||||
{ id: "dh", name: "DH", direction: "input", kind: "number" },
|
||||
{ id: "flip_x", name: "Flip X", direction: "input", kind: "boolean" },
|
||||
{ id: "flip_y", name: "Flip Y", direction: "input", kind: "boolean" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const sx = resolveValueInput("sx", "0");
|
||||
const sy = resolveValueInput("sy", "0");
|
||||
const sw = resolveValueInput("sw", "8");
|
||||
const sh = resolveValueInput("sh", "8");
|
||||
const dx = resolveValueInput("dx", "0");
|
||||
const dy = resolveValueInput("dy", "0");
|
||||
const dw = resolveValueInput("dw", OMIT);
|
||||
const dh = resolveValueInput("dh", OMIT);
|
||||
const flipX = resolveValueInput("flip_x", OMIT);
|
||||
const flipY = resolveValueInput("flip_y", OMIT);
|
||||
|
||||
const args = [sx, sy, sw, sh, dx, dy];
|
||||
const includeDestSize =
|
||||
dw !== OMIT || dh !== OMIT || flipX !== OMIT || flipY !== OMIT;
|
||||
|
||||
if (includeDestSize) {
|
||||
args.push(dw === OMIT ? sw : dw, dh === OMIT ? sh : dh);
|
||||
}
|
||||
|
||||
if (flipX !== OMIT || flipY !== OMIT) {
|
||||
args.push(
|
||||
flipX === OMIT ? "false" : flipX,
|
||||
flipY === OMIT ? "false" : flipY
|
||||
);
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}sspr(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
67
scripts/nodes/library/ifNode.js
Normal file
67
scripts/nodes/library/ifNode.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Branches execution based on a boolean condition.
|
||||
*/
|
||||
export const ifNode = createNodeModule(
|
||||
{
|
||||
id: "if",
|
||||
title: "If",
|
||||
category: "Logic",
|
||||
description: "Branch execution based on a boolean condition.",
|
||||
searchTags: ["if", "condition", "branch", "logic"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "condition",
|
||||
name: "Condition",
|
||||
direction: "input",
|
||||
kind: "boolean",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "then", name: "Then", direction: "output", kind: "exec" },
|
||||
{ id: "else", name: "Else", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({
|
||||
indent,
|
||||
indentLevel,
|
||||
resolveValueInput,
|
||||
emitBranch,
|
||||
findExecTargets,
|
||||
path,
|
||||
}) => {
|
||||
const condition = resolveValueInput("condition", "false");
|
||||
const lines = [`${indent(indentLevel)}if ${condition} then`];
|
||||
|
||||
const thenLines = emitBranch("then", {
|
||||
indentLevel: indentLevel + 1,
|
||||
path: new Set(path),
|
||||
});
|
||||
if (!thenLines.length) {
|
||||
lines.push(`${indent(indentLevel + 1)}-- then branch`);
|
||||
} else {
|
||||
lines.push(...thenLines);
|
||||
}
|
||||
|
||||
if (findExecTargets("else").length) {
|
||||
lines.push(`${indent(indentLevel)}else`);
|
||||
const elseLines = emitBranch("else", {
|
||||
indentLevel: indentLevel + 1,
|
||||
path: new Set(path),
|
||||
});
|
||||
if (!elseLines.length) {
|
||||
lines.push(`${indent(indentLevel + 1)}-- else branch`);
|
||||
} else {
|
||||
lines.push(...elseLines);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(`${indent(indentLevel)}end`);
|
||||
return lines;
|
||||
},
|
||||
}
|
||||
);
|
||||
87
scripts/nodes/library/index.js
Normal file
87
scripts/nodes/library/index.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import { eventInitNode } from "./eventStart.js";
|
||||
import { eventUpdateNode } from "./eventUpdate.js";
|
||||
import { eventDrawNode } from "./eventDraw.js";
|
||||
import { printNode } from "./print.js";
|
||||
import { setVariableNode } from "./setVariable.js";
|
||||
import { getVariableNode } from "./getVariable.js";
|
||||
import { numberLiteralNode } from "./numberLiteral.js";
|
||||
import { stringLiteralNode } from "./stringLiteral.js";
|
||||
import { booleanLiteralNode } from "./booleanLiteral.js";
|
||||
import { addNumberNode } from "./addNumber.js";
|
||||
import { multiplyNumberNode } from "./multiplyNumber.js";
|
||||
import { compareNode } from "./compare.js";
|
||||
import { sequenceNode } from "./sequence.js";
|
||||
import { ifNode } from "./ifNode.js";
|
||||
import { forLoopNode } from "./forLoop.js";
|
||||
import { customEventNode } from "./customEvent.js";
|
||||
import { callCustomEventNode } from "./callCustomEvent.js";
|
||||
import { graphicsNodes, graphicsFunctionChecklist } from "./graphics/index.js";
|
||||
import { systemNodes, systemFunctionChecklist } from "./system/index.js";
|
||||
import { tableNodes, tableFunctionChecklist } from "./table/index.js";
|
||||
import { inputNodes, inputFunctionChecklist } from "./input/index.js";
|
||||
import { audioNodes, audioFunctionChecklist } from "./audio/index.js";
|
||||
import { mapNodes, mapFunctionChecklist } from "./map/index.js";
|
||||
import { memoryNodes, memoryFunctionChecklist } from "./memory/index.js";
|
||||
import { mathNodes, mathFunctionChecklist } from "./math/index.js";
|
||||
import { menuNodes, menuFunctionChecklist } from "./menu/index.js";
|
||||
import { stringNodes, stringFunctionChecklist } from "./strings/index.js";
|
||||
import { dataNodes, dataFunctionChecklist } from "./data/index.js";
|
||||
import { gpioNodes, gpioFunctionChecklist } from "./gpio/index.js";
|
||||
import { serialNodes, serialFunctionChecklist } from "./serial/index.js";
|
||||
import { devkitNodes, devkitFunctionChecklist } from "./devkit/index.js";
|
||||
import { luaNodes, luaFunctionChecklist } from "./lua/index.js";
|
||||
import { localVariableNodes } from "./localVariables.js";
|
||||
|
||||
export const nodeModules = [
|
||||
eventInitNode,
|
||||
eventUpdateNode,
|
||||
eventDrawNode,
|
||||
printNode,
|
||||
setVariableNode,
|
||||
getVariableNode,
|
||||
numberLiteralNode,
|
||||
stringLiteralNode,
|
||||
booleanLiteralNode,
|
||||
addNumberNode,
|
||||
multiplyNumberNode,
|
||||
compareNode,
|
||||
sequenceNode,
|
||||
ifNode,
|
||||
forLoopNode,
|
||||
customEventNode,
|
||||
callCustomEventNode,
|
||||
...localVariableNodes,
|
||||
...graphicsNodes,
|
||||
...systemNodes,
|
||||
...tableNodes,
|
||||
...inputNodes,
|
||||
...audioNodes,
|
||||
...mapNodes,
|
||||
...memoryNodes,
|
||||
...mathNodes,
|
||||
...menuNodes,
|
||||
...stringNodes,
|
||||
...dataNodes,
|
||||
...gpioNodes,
|
||||
...serialNodes,
|
||||
...devkitNodes,
|
||||
...luaNodes,
|
||||
];
|
||||
|
||||
export {
|
||||
systemFunctionChecklist,
|
||||
graphicsFunctionChecklist,
|
||||
tableFunctionChecklist,
|
||||
inputFunctionChecklist,
|
||||
audioFunctionChecklist,
|
||||
mapFunctionChecklist,
|
||||
memoryFunctionChecklist,
|
||||
mathFunctionChecklist,
|
||||
menuFunctionChecklist,
|
||||
stringFunctionChecklist,
|
||||
dataFunctionChecklist,
|
||||
gpioFunctionChecklist,
|
||||
serialFunctionChecklist,
|
||||
devkitFunctionChecklist,
|
||||
luaFunctionChecklist,
|
||||
};
|
||||
55
scripts/nodes/library/input/btn.js
Normal file
55
scripts/nodes/library/input/btn.js
Normal file
@@ -0,0 +1,55 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads the state of a PICO-8 button using BTN.
|
||||
*/
|
||||
export const btnNode = createNodeModule(
|
||||
{
|
||||
id: "input_btn",
|
||||
title: "Button State",
|
||||
category: "Input",
|
||||
description: "Read the current pressed state for a controller button.",
|
||||
searchTags: ["btn", "input", "button", "press"],
|
||||
inputs: [
|
||||
{
|
||||
id: "button",
|
||||
name: "Button",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Button index or glyph code",
|
||||
},
|
||||
{
|
||||
id: "player",
|
||||
name: "Player",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Optional player index",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Pressed", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const button = resolveValueInput("button", OMIT);
|
||||
const player = resolveValueInput("player", OMIT);
|
||||
|
||||
if (button === OMIT && player === OMIT) {
|
||||
return "btn()";
|
||||
}
|
||||
|
||||
if (button === OMIT) {
|
||||
return `btn(nil, ${player})`;
|
||||
}
|
||||
|
||||
if (player === OMIT) {
|
||||
return `btn(${button})`;
|
||||
}
|
||||
|
||||
return `btn(${button}, ${player})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
56
scripts/nodes/library/input/btnp.js
Normal file
56
scripts/nodes/library/input/btnp.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads button press transitions using BTNP.
|
||||
*/
|
||||
export const btnpNode = createNodeModule(
|
||||
{
|
||||
id: "input_btnp",
|
||||
title: "Button Press",
|
||||
category: "Input",
|
||||
description:
|
||||
"Detect when a button is initially pressed or repeats using BTNP.",
|
||||
searchTags: ["btnp", "input", "button", "press", "repeat"],
|
||||
inputs: [
|
||||
{
|
||||
id: "button",
|
||||
name: "Button",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Button index or glyph code",
|
||||
},
|
||||
{
|
||||
id: "player",
|
||||
name: "Player",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Optional player index",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Pressed", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const button = resolveValueInput("button", OMIT);
|
||||
const player = resolveValueInput("player", OMIT);
|
||||
|
||||
if (button === OMIT && player === OMIT) {
|
||||
return "btnp()";
|
||||
}
|
||||
|
||||
if (button === OMIT) {
|
||||
return `btnp(nil, ${player})`;
|
||||
}
|
||||
|
||||
if (player === OMIT) {
|
||||
return `btnp(${button})`;
|
||||
}
|
||||
|
||||
return `btnp(${button}, ${player})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
175
scripts/nodes/library/input/controllerButtons.js
Normal file
175
scripts/nodes/library/input/controllerButtons.js
Normal file
@@ -0,0 +1,175 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
const DEFAULT_PLAYER_VALUE = "0";
|
||||
|
||||
/**
|
||||
* Normalizes the stored player property to a valid controller index.
|
||||
*
|
||||
* @param {unknown} value Raw inspector value.
|
||||
* @returns {number}
|
||||
*/
|
||||
const resolvePlayerIndex = (value) => {
|
||||
const numeric = Number.parseInt(
|
||||
typeof value === "string" ? value : String(value ?? DEFAULT_PLAYER_VALUE),
|
||||
10
|
||||
);
|
||||
if (!Number.isFinite(numeric) || numeric < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (numeric > 7) {
|
||||
return 7;
|
||||
}
|
||||
return numeric;
|
||||
};
|
||||
|
||||
const BUTTON_OPTIONS = [
|
||||
{ value: "0", label: "Left" },
|
||||
{ value: "1", label: "Right" },
|
||||
{ value: "2", label: "Up" },
|
||||
{ value: "3", label: "Down" },
|
||||
{ value: "4", label: "O" },
|
||||
{ value: "5", label: "X" },
|
||||
];
|
||||
|
||||
const BUTTON_VALUE_SET = new Set(BUTTON_OPTIONS.map((option) => option.value));
|
||||
|
||||
const DEFAULT_BUTTON_VALUE = BUTTON_OPTIONS[0]?.value ?? "0";
|
||||
|
||||
/**
|
||||
* Normalizes button property values to a valid BTN index represented as a string.
|
||||
*
|
||||
* @param {unknown} value Raw inspector value.
|
||||
* @returns {string}
|
||||
*/
|
||||
const resolveButtonValue = (value) => {
|
||||
const numeric = Number.parseInt(
|
||||
typeof value === "string" ? value : String(value ?? DEFAULT_BUTTON_VALUE),
|
||||
10
|
||||
);
|
||||
|
||||
const candidate = Number.isFinite(numeric) ? String(numeric) : DEFAULT_BUTTON_VALUE;
|
||||
if (BUTTON_VALUE_SET.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
return DEFAULT_BUTTON_VALUE;
|
||||
};
|
||||
|
||||
const PLAYER_OPTIONS = [
|
||||
{ value: "0", label: "Player 0 (Cursors / Z X)" },
|
||||
{ value: "1", label: "Player 1 (SFED / Tab Q W A)" },
|
||||
{ value: "2", label: "Player 2" },
|
||||
{ value: "3", label: "Player 3" },
|
||||
{ value: "4", label: "Player 4" },
|
||||
{ value: "5", label: "Player 5" },
|
||||
{ value: "6", label: "Player 6" },
|
||||
{ value: "7", label: "Player 7" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Evaluates whether the configured button is currently pressed.
|
||||
*/
|
||||
export const controllerButtonsNode = createNodeModule(
|
||||
{
|
||||
id: "input_button_constants",
|
||||
title: "Named Button",
|
||||
category: "Input",
|
||||
description:
|
||||
"Read a specific controller button with a dropdown selector and optional player override.",
|
||||
searchTags: [
|
||||
"button",
|
||||
"controller",
|
||||
"dpad",
|
||||
"input",
|
||||
"player",
|
||||
"named",
|
||||
"press",
|
||||
],
|
||||
inputs: [
|
||||
{
|
||||
id: "button",
|
||||
name: "Button",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Optional button index override",
|
||||
},
|
||||
{
|
||||
id: "player",
|
||||
name: "Player",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Optional player index override",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
id: "pressed",
|
||||
name: "Pressed",
|
||||
direction: "output",
|
||||
kind: "boolean",
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
key: "button",
|
||||
label: "Button",
|
||||
type: "enum",
|
||||
defaultValue: DEFAULT_BUTTON_VALUE,
|
||||
options: BUTTON_OPTIONS,
|
||||
},
|
||||
{
|
||||
key: "player",
|
||||
label: "Default Player",
|
||||
type: "enum",
|
||||
defaultValue: DEFAULT_PLAYER_VALUE,
|
||||
options: PLAYER_OPTIONS,
|
||||
},
|
||||
],
|
||||
initializeProperties: (properties) => {
|
||||
properties.button = resolveButtonValue(properties.button);
|
||||
const normalizedPlayer = resolvePlayerIndex(properties.player);
|
||||
properties.player = String(normalizedPlayer);
|
||||
const buttonInlineKey = "pin:button";
|
||||
const playerInlineKey = "pin:player";
|
||||
const numericButton = Number.parseInt(properties.button, 10);
|
||||
const numericPlayer = normalizedPlayer;
|
||||
properties[buttonInlineKey] = Number.isFinite(numericButton)
|
||||
? numericButton
|
||||
: 0;
|
||||
properties[playerInlineKey] = Number.isFinite(numericPlayer)
|
||||
? numericPlayer
|
||||
: 0;
|
||||
},
|
||||
onPropertiesChanged: (properties) => {
|
||||
properties.button = resolveButtonValue(properties.button);
|
||||
const normalizedPlayer = resolvePlayerIndex(properties.player);
|
||||
properties.player = String(normalizedPlayer);
|
||||
const buttonInlineKey = "pin:button";
|
||||
const playerInlineKey = "pin:player";
|
||||
const numericButton = Number.parseInt(properties.button, 10);
|
||||
const numericPlayer = normalizedPlayer;
|
||||
properties[buttonInlineKey] = Number.isFinite(numericButton)
|
||||
? numericButton
|
||||
: 0;
|
||||
properties[playerInlineKey] = Number.isFinite(numericPlayer)
|
||||
? numericPlayer
|
||||
: 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ node, resolveValueInput, formatLiteral }) => {
|
||||
const buttonValue = resolveButtonValue(node.properties.button);
|
||||
const parsedButton = Number.parseInt(buttonValue, 10);
|
||||
const buttonIndex = Number.isFinite(parsedButton) ? parsedButton : 0;
|
||||
const fallbackButton = formatLiteral("number", buttonIndex);
|
||||
const buttonInput = resolveValueInput("button", fallbackButton);
|
||||
|
||||
const fallbackPlayer = formatLiteral(
|
||||
"number",
|
||||
resolvePlayerIndex(node.properties.player)
|
||||
);
|
||||
const playerInput = resolveValueInput("player", fallbackPlayer);
|
||||
|
||||
return `btn(${buttonInput}, ${playerInput})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
17
scripts/nodes/library/input/index.js
Normal file
17
scripts/nodes/library/input/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { btnNode } from "./btn.js";
|
||||
import { btnpNode } from "./btnp.js";
|
||||
import { controllerButtonsNode } from "./controllerButtons.js";
|
||||
|
||||
/**
|
||||
* All input-related node modules backed by PICO-8 helpers.
|
||||
*/
|
||||
export const inputNodes = [controllerButtonsNode, btnNode, btnpNode];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented input helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const inputFunctionChecklist = [
|
||||
{ function: "BTN", nodeId: "input_btn", implemented: true },
|
||||
{ function: "BTNP", nodeId: "input_btnp", implemented: true },
|
||||
];
|
||||
0
scripts/nodes/library/localVariable.js
Normal file
0
scripts/nodes/library/localVariable.js
Normal file
169
scripts/nodes/library/localVariables.js
Normal file
169
scripts/nodes/library/localVariables.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import { createNodeModule } from "../nodeTypes.js";
|
||||
|
||||
const LOCAL_TYPES = [
|
||||
{ value: "number", label: "Number" },
|
||||
{ value: "string", label: "String" },
|
||||
{ value: "boolean", label: "Boolean" },
|
||||
{ value: "table", label: "Table" },
|
||||
];
|
||||
|
||||
const DEFAULT_LOCAL_TYPE = LOCAL_TYPES[0]?.value ?? "number";
|
||||
|
||||
const sanitizeType = (raw) => {
|
||||
const candidate = typeof raw === "string" ? raw.toLowerCase() : DEFAULT_LOCAL_TYPE;
|
||||
return LOCAL_TYPES.some((entry) => entry.value === candidate)
|
||||
? candidate
|
||||
: DEFAULT_LOCAL_TYPE;
|
||||
};
|
||||
|
||||
const ensureName = (raw, fallback = "localVar") => {
|
||||
const value = typeof raw === "string" ? raw.trim() : "";
|
||||
return value.length ? value : fallback;
|
||||
};
|
||||
|
||||
const resolveValuePropertyKey = (type) => {
|
||||
switch (type) {
|
||||
case "string":
|
||||
return "valueString";
|
||||
case "boolean":
|
||||
return "valueBoolean";
|
||||
case "table":
|
||||
return "valueTable";
|
||||
case "number":
|
||||
default:
|
||||
return "valueNumber";
|
||||
}
|
||||
};
|
||||
|
||||
const defaultValueForType = (type) => {
|
||||
switch (type) {
|
||||
case "string":
|
||||
return "";
|
||||
case "boolean":
|
||||
return false;
|
||||
case "table":
|
||||
return "{}";
|
||||
case "number":
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const initializeLocalProperties = (properties, { includeValue }) => {
|
||||
const type = sanitizeType(properties.variableType);
|
||||
properties.variableType = type;
|
||||
properties.name = ensureName(properties.name);
|
||||
|
||||
if (typeof properties.valueNumber !== "number") {
|
||||
properties.valueNumber = Number.isFinite(Number(properties.valueNumber))
|
||||
? Number(properties.valueNumber)
|
||||
: 0;
|
||||
}
|
||||
if (typeof properties.valueString !== "string") {
|
||||
properties.valueString = String(properties.valueString ?? "");
|
||||
}
|
||||
if (typeof properties.valueBoolean !== "boolean") {
|
||||
properties.valueBoolean = Boolean(properties.valueBoolean);
|
||||
}
|
||||
if (typeof properties.valueTable !== "string") {
|
||||
properties.valueTable = String(properties.valueTable ?? "{}");
|
||||
}
|
||||
|
||||
if (includeValue) {
|
||||
const key = resolveValuePropertyKey(type);
|
||||
const value = properties[key] ?? defaultValueForType(type);
|
||||
properties[key] = value;
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeLocalProperties = (properties, { includeValue }) => {
|
||||
const type = sanitizeType(properties.variableType);
|
||||
properties.variableType = type;
|
||||
properties.name = ensureName(properties.name);
|
||||
if (includeValue) {
|
||||
const key = resolveValuePropertyKey(type);
|
||||
const value = properties[key] ?? defaultValueForType(type);
|
||||
properties[key] = value;
|
||||
}
|
||||
};
|
||||
|
||||
export const setLocalVariableNode = createNodeModule(
|
||||
{
|
||||
id: "set_local_var",
|
||||
title: "Set Local",
|
||||
category: "Logic",
|
||||
description: "Declare or assign a local variable with an inline literal.",
|
||||
searchTags: ["local", "set", "assign", "variable"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Optional override for the inline literal",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
initializeProperties: (properties) => {
|
||||
initializeLocalProperties(properties, { includeValue: true });
|
||||
},
|
||||
onPropertiesChanged: (properties) => {
|
||||
normalizeLocalProperties(properties, { includeValue: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
emitExec: ({
|
||||
node,
|
||||
indent,
|
||||
indentLevel,
|
||||
resolveValueInput,
|
||||
sanitizeIdentifier,
|
||||
emitNextExec,
|
||||
formatLiteral,
|
||||
}) => {
|
||||
const type = sanitizeType(node.properties.variableType);
|
||||
const name = sanitizeIdentifier(ensureName(node.properties.name));
|
||||
const valueKey = resolveValuePropertyKey(type);
|
||||
const inlineValue = node.properties[valueKey] ?? defaultValueForType(type);
|
||||
const fallbackLiteral =
|
||||
type === "table"
|
||||
? String(inlineValue ?? "{}") || "{}"
|
||||
: formatLiteral(type, inlineValue);
|
||||
const valueExpression = resolveValueInput("value", fallbackLiteral);
|
||||
const statement = `${indent(indentLevel)}local ${name} = ${valueExpression}`;
|
||||
return [statement, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const getLocalVariableNode = createNodeModule(
|
||||
{
|
||||
id: "get_local_var",
|
||||
title: "Get Local",
|
||||
category: "Logic",
|
||||
description: "Access a local variable declared earlier in the flow.",
|
||||
searchTags: ["local", "get", "variable", "read"],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "any" },
|
||||
],
|
||||
properties: [],
|
||||
initializeProperties: (properties) => {
|
||||
initializeLocalProperties(properties, { includeValue: false });
|
||||
},
|
||||
onPropertiesChanged: (properties) => {
|
||||
normalizeLocalProperties(properties, { includeValue: false });
|
||||
},
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ node, sanitizeIdentifier }) => {
|
||||
return sanitizeIdentifier(ensureName(node.properties.name));
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const localVariableNodes = [setLocalVariableNode, getLocalVariableNode];
|
||||
38
scripts/nodes/library/lua/coroutineCreate.js
Normal file
38
scripts/nodes/library/lua/coroutineCreate.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Creates a coroutine from a function using coroutine.create().
|
||||
*/
|
||||
export const luaCoroutineCreateNode = createNodeModule(
|
||||
{
|
||||
id: "lua_coroutine_create",
|
||||
title: "Coroutine Create",
|
||||
category: "Lua",
|
||||
description: "Wrap a function in a coroutine via coroutine.create().",
|
||||
searchTags: ["coroutine", "create", "lua"],
|
||||
inputs: [
|
||||
{
|
||||
id: "fn",
|
||||
name: "Function",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Function to run inside the coroutine",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
id: "coroutine",
|
||||
name: "Coroutine",
|
||||
direction: "output",
|
||||
kind: "any",
|
||||
},
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const fn = resolveValueInput("fn", "function() end");
|
||||
return `coroutine.create(${fn})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
91
scripts/nodes/library/lua/coroutineResume.js
Normal file
91
scripts/nodes/library/lua/coroutineResume.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Resumes a coroutine and exposes the success flag and first result.
|
||||
*/
|
||||
export const luaCoroutineResumeNode = createNodeModule(
|
||||
{
|
||||
id: "lua_coroutine_resume",
|
||||
title: "Coroutine Resume",
|
||||
category: "Lua",
|
||||
description:
|
||||
"Resume a coroutine and capture the success flag alongside the first returned value.",
|
||||
searchTags: ["coroutine", "resume", "lua"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "coroutine",
|
||||
name: "Coroutine",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
},
|
||||
{
|
||||
id: "arg1",
|
||||
name: "Arg 1",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Optional first value passed to coroutine",
|
||||
},
|
||||
{
|
||||
id: "arg2",
|
||||
name: "Arg 2",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Optional second value passed to coroutine",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
{
|
||||
id: "success",
|
||||
name: "Success",
|
||||
direction: "output",
|
||||
kind: "boolean",
|
||||
},
|
||||
{
|
||||
id: "result",
|
||||
name: "Result",
|
||||
direction: "output",
|
||||
kind: "any",
|
||||
},
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({
|
||||
node,
|
||||
indent,
|
||||
indentLevel,
|
||||
resolveValueInput,
|
||||
sanitizeIdentifier,
|
||||
emitNextExec,
|
||||
}) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const target = resolveValueInput("coroutine", "nil");
|
||||
const arg1 = resolveValueInput("arg1", OMIT);
|
||||
const arg2 = resolveValueInput("arg2", OMIT);
|
||||
const args = [target];
|
||||
if (arg1 !== OMIT) {
|
||||
args.push(arg1);
|
||||
}
|
||||
if (arg2 !== OMIT) {
|
||||
args.push(arg2);
|
||||
}
|
||||
const successName = sanitizeIdentifier(`__pg_${node.id}_co_success`);
|
||||
const resultName = sanitizeIdentifier(`__pg_${node.id}_co_result`);
|
||||
const line = `${indent(indentLevel)}local ${successName}, ${resultName} = coroutine.resume(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
evaluateValue: ({ node, pinId, sanitizeIdentifier }) => {
|
||||
const successName = sanitizeIdentifier(`__pg_${node.id}_co_success`);
|
||||
const resultName = sanitizeIdentifier(`__pg_${node.id}_co_result`);
|
||||
if (pinId === "success") {
|
||||
return successName;
|
||||
}
|
||||
if (pinId === "result") {
|
||||
return resultName;
|
||||
}
|
||||
return "nil";
|
||||
},
|
||||
}
|
||||
);
|
||||
27
scripts/nodes/library/lua/coroutineRunning.js
Normal file
27
scripts/nodes/library/lua/coroutineRunning.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Returns the currently running coroutine via coroutine.running().
|
||||
*/
|
||||
export const luaCoroutineRunningNode = createNodeModule(
|
||||
{
|
||||
id: "lua_coroutine_running",
|
||||
title: "Coroutine Running",
|
||||
category: "Lua",
|
||||
description: "Access the currently running coroutine reference.",
|
||||
searchTags: ["coroutine", "running", "lua"],
|
||||
inputs: [],
|
||||
outputs: [
|
||||
{
|
||||
id: "coroutine",
|
||||
name: "Coroutine",
|
||||
direction: "output",
|
||||
kind: "any",
|
||||
},
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: () => "coroutine.running()",
|
||||
}
|
||||
);
|
||||
32
scripts/nodes/library/lua/coroutineStatus.js
Normal file
32
scripts/nodes/library/lua/coroutineStatus.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reports a coroutine's status via coroutine.status().
|
||||
*/
|
||||
export const luaCoroutineStatusNode = createNodeModule(
|
||||
{
|
||||
id: "lua_coroutine_status",
|
||||
title: "Coroutine Status",
|
||||
category: "Lua",
|
||||
description: "Retrieve a coroutine's status string using coroutine.status().",
|
||||
searchTags: ["coroutine", "status", "lua"],
|
||||
inputs: [
|
||||
{
|
||||
id: "coroutine",
|
||||
name: "Coroutine",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "status", name: "Status", direction: "output", kind: "string" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const target = resolveValueInput("coroutine", "nil");
|
||||
return `coroutine.status(${target})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
53
scripts/nodes/library/lua/coroutineYield.js
Normal file
53
scripts/nodes/library/lua/coroutineYield.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Yields from within a coroutine using coroutine.yield().
|
||||
*/
|
||||
export const luaCoroutineYieldNode = createNodeModule(
|
||||
{
|
||||
id: "lua_coroutine_yield",
|
||||
title: "Coroutine Yield",
|
||||
category: "Lua",
|
||||
description: "Yield execution from within a coroutine and optionally return values.",
|
||||
searchTags: ["coroutine", "yield", "lua"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "value1",
|
||||
name: "Value 1",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Optional first value yielded to the caller",
|
||||
},
|
||||
{
|
||||
id: "value2",
|
||||
name: "Value 2",
|
||||
direction: "input",
|
||||
kind: "any",
|
||||
description: "Optional second value yielded to the caller",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const value1 = resolveValueInput("value1", OMIT);
|
||||
const value2 = resolveValueInput("value2", OMIT);
|
||||
const args = [];
|
||||
if (value1 !== OMIT) {
|
||||
args.push(value1);
|
||||
}
|
||||
if (value2 !== OMIT) {
|
||||
args.push(value2);
|
||||
}
|
||||
const call = args.length
|
||||
? `${indent(indentLevel)}coroutine.yield(${args.join(", ")})`
|
||||
: `${indent(indentLevel)}coroutine.yield()`;
|
||||
return [call, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
27
scripts/nodes/library/lua/getMetatable.js
Normal file
27
scripts/nodes/library/lua/getMetatable.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Retrieves the metatable associated with a table via getmetatable().
|
||||
*/
|
||||
export const luaGetMetatableNode = createNodeModule(
|
||||
{
|
||||
id: "lua_getmetatable",
|
||||
title: "Get Metatable",
|
||||
category: "Lua",
|
||||
description: "Fetch a table's metatable using getmetatable().",
|
||||
searchTags: ["getmetatable", "metatable", "lua"],
|
||||
inputs: [
|
||||
{ id: "table", name: "Table", direction: "input", kind: "table" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "metatable", name: "Metatable", direction: "output", kind: "table" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const tableValue = resolveValueInput("table", "{}");
|
||||
return `getmetatable(${tableValue})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
63
scripts/nodes/library/lua/index.js
Normal file
63
scripts/nodes/library/lua/index.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import { luaSetMetatableNode } from "./setMetatable.js";
|
||||
import { luaGetMetatableNode } from "./getMetatable.js";
|
||||
import { luaRawGetNode } from "./rawGet.js";
|
||||
import { luaRawSetNode } from "./rawSet.js";
|
||||
import { luaRawEqualNode } from "./rawEqual.js";
|
||||
import { luaCoroutineCreateNode } from "./coroutineCreate.js";
|
||||
import { luaCoroutineResumeNode } from "./coroutineResume.js";
|
||||
import { luaCoroutineYieldNode } from "./coroutineYield.js";
|
||||
import { luaCoroutineStatusNode } from "./coroutineStatus.js";
|
||||
import { luaCoroutineRunningNode } from "./coroutineRunning.js";
|
||||
|
||||
/**
|
||||
* Node modules covering Lua metatable and coroutine helpers.
|
||||
*/
|
||||
export const luaNodes = [
|
||||
luaSetMetatableNode,
|
||||
luaGetMetatableNode,
|
||||
luaRawGetNode,
|
||||
luaRawSetNode,
|
||||
luaRawEqualNode,
|
||||
luaCoroutineCreateNode,
|
||||
luaCoroutineResumeNode,
|
||||
luaCoroutineYieldNode,
|
||||
luaCoroutineStatusNode,
|
||||
luaCoroutineRunningNode,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of Lua metatable and coroutine helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const luaFunctionChecklist = [
|
||||
{ function: "SETMETATABLE", nodeId: "lua_setmetatable", implemented: true },
|
||||
{ function: "GETMETATABLE", nodeId: "lua_getmetatable", implemented: true },
|
||||
{ function: "RAWGET", nodeId: "lua_rawget", implemented: true },
|
||||
{ function: "RAWSET", nodeId: "lua_rawset", implemented: true },
|
||||
{ function: "RAWEQUAL", nodeId: "lua_rawequal", implemented: true },
|
||||
{
|
||||
function: "COROUTINE.CREATE",
|
||||
nodeId: "lua_coroutine_create",
|
||||
implemented: true,
|
||||
},
|
||||
{
|
||||
function: "COROUTINE.RESUME",
|
||||
nodeId: "lua_coroutine_resume",
|
||||
implemented: true,
|
||||
},
|
||||
{
|
||||
function: "COROUTINE.YIELD",
|
||||
nodeId: "lua_coroutine_yield",
|
||||
implemented: true,
|
||||
},
|
||||
{
|
||||
function: "COROUTINE.STATUS",
|
||||
nodeId: "lua_coroutine_status",
|
||||
implemented: true,
|
||||
},
|
||||
{
|
||||
function: "COROUTINE.RUNNING",
|
||||
nodeId: "lua_coroutine_running",
|
||||
implemented: true,
|
||||
},
|
||||
];
|
||||
29
scripts/nodes/library/lua/rawEqual.js
Normal file
29
scripts/nodes/library/lua/rawEqual.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Compares two values using rawequal().
|
||||
*/
|
||||
export const luaRawEqualNode = createNodeModule(
|
||||
{
|
||||
id: "lua_rawequal",
|
||||
title: "Raw Equal",
|
||||
category: "Lua",
|
||||
description: "Compare values without metamethods using rawequal().",
|
||||
searchTags: ["rawequal", "compare", "lua"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "any" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "any" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "equal", name: "Equal", direction: "output", kind: "boolean" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "nil");
|
||||
const b = resolveValueInput("b", "nil");
|
||||
return `rawequal(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/lua/rawGet.js
Normal file
29
scripts/nodes/library/lua/rawGet.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Retrieves a raw table entry using rawget().
|
||||
*/
|
||||
export const luaRawGetNode = createNodeModule(
|
||||
{
|
||||
id: "lua_rawget",
|
||||
title: "Raw Get",
|
||||
category: "Lua",
|
||||
description: "Access a table entry without metamethods using rawget().",
|
||||
searchTags: ["rawget", "table", "lua"],
|
||||
inputs: [
|
||||
{ id: "table", name: "Table", direction: "input", kind: "table" },
|
||||
{ id: "key", name: "Key", direction: "input", kind: "any" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "any" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const tableValue = resolveValueInput("table", "{}");
|
||||
const key = resolveValueInput("key", "nil");
|
||||
return `rawget(${tableValue}, ${key})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
33
scripts/nodes/library/lua/rawSet.js
Normal file
33
scripts/nodes/library/lua/rawSet.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Writes a raw table entry using rawset().
|
||||
*/
|
||||
export const luaRawSetNode = createNodeModule(
|
||||
{
|
||||
id: "lua_rawset",
|
||||
title: "Raw Set",
|
||||
category: "Lua",
|
||||
description: "Assign a table entry without metamethods using rawset().",
|
||||
searchTags: ["rawset", "table", "lua"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "table", name: "Table", direction: "input", kind: "table" },
|
||||
{ id: "key", name: "Key", direction: "input", kind: "any" },
|
||||
{ id: "value", name: "Value", direction: "input", kind: "any" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const tableValue = resolveValueInput("table", "{}");
|
||||
const key = resolveValueInput("key", "nil");
|
||||
const value = resolveValueInput("value", "nil");
|
||||
const line = `${indent(indentLevel)}rawset(${tableValue}, ${key}, ${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
35
scripts/nodes/library/lua/setMetatable.js
Normal file
35
scripts/nodes/library/lua/setMetatable.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Applies a metatable to a table via setmetatable().
|
||||
*/
|
||||
export const luaSetMetatableNode = createNodeModule(
|
||||
{
|
||||
id: "lua_setmetatable",
|
||||
title: "Set Metatable",
|
||||
category: "Lua",
|
||||
description: "Assign a metatable to a table using setmetatable().",
|
||||
searchTags: ["setmetatable", "metatable", "lua"],
|
||||
inputs: [
|
||||
{ id: "table", name: "Table", direction: "input", kind: "table" },
|
||||
{
|
||||
id: "metatable",
|
||||
name: "Metatable",
|
||||
direction: "input",
|
||||
kind: "table",
|
||||
defaultValue: "nil",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "table_out", name: "Table", direction: "output", kind: "table" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const tableValue = resolveValueInput("table", "{}");
|
||||
const metatable = resolveValueInput("metatable", "nil");
|
||||
return `setmetatable(${tableValue}, ${metatable})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
20
scripts/nodes/library/map/index.js
Normal file
20
scripts/nodes/library/map/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { mapMgetNode } from "./mget.js";
|
||||
import { mapMsetNode } from "./mset.js";
|
||||
import { mapDrawNode } from "./map.js";
|
||||
import { mapTlineNode } from "./tline.js";
|
||||
|
||||
/**
|
||||
* All map-related node modules backed by PICO-8 helpers.
|
||||
*/
|
||||
export const mapNodes = [mapMgetNode, mapMsetNode, mapDrawNode, mapTlineNode];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented map helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const mapFunctionChecklist = [
|
||||
{ function: "MGET", nodeId: "map_mget", implemented: true },
|
||||
{ function: "MSET", nodeId: "map_mset", implemented: true },
|
||||
{ function: "MAP", nodeId: "map_map", implemented: true },
|
||||
{ function: "TLINE", nodeId: "map_tline", implemented: true },
|
||||
];
|
||||
108
scripts/nodes/library/map/map.js
Normal file
108
scripts/nodes/library/map/map.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws map tiles using PICO-8's map helper.
|
||||
*/
|
||||
export const mapDrawNode = createNodeModule(
|
||||
{
|
||||
id: "map_map",
|
||||
title: "Draw Map",
|
||||
category: "Map",
|
||||
description:
|
||||
"Render a section of the map to the screen with optional layers.",
|
||||
searchTags: ["map", "draw", "tiles", "layer"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "tile_x",
|
||||
name: "Tile X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Starting tile X coordinate",
|
||||
},
|
||||
{
|
||||
id: "tile_y",
|
||||
name: "Tile Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Starting tile Y coordinate",
|
||||
},
|
||||
{
|
||||
id: "screen_x",
|
||||
name: "Screen X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Screen X position in pixels",
|
||||
},
|
||||
{
|
||||
id: "screen_y",
|
||||
name: "Screen Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Screen Y position in pixels",
|
||||
},
|
||||
{
|
||||
id: "tile_w",
|
||||
name: "Tile Width",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Number of tiles across",
|
||||
},
|
||||
{
|
||||
id: "tile_h",
|
||||
name: "Tile Height",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Number of tiles down",
|
||||
},
|
||||
{
|
||||
id: "layers",
|
||||
name: "Layers",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Sprite flag bitmask",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const tileX = resolveValueInput("tile_x", OMIT);
|
||||
const tileY = resolveValueInput("tile_y", OMIT);
|
||||
const screenX = resolveValueInput("screen_x", OMIT);
|
||||
const screenY = resolveValueInput("screen_y", OMIT);
|
||||
const tileW = resolveValueInput("tile_w", OMIT);
|
||||
const tileH = resolveValueInput("tile_h", OMIT);
|
||||
const layers = resolveValueInput("layers", OMIT);
|
||||
|
||||
const values = [
|
||||
{ value: tileX, placeholder: "0" },
|
||||
{ value: tileY, placeholder: "0" },
|
||||
{ value: screenX, placeholder: "0" },
|
||||
{ value: screenY, placeholder: "0" },
|
||||
{ value: tileW, placeholder: "nil" },
|
||||
{ value: tileH, placeholder: "nil" },
|
||||
{ value: layers, placeholder: "nil" },
|
||||
];
|
||||
|
||||
let lastIndex = values.length - 1;
|
||||
while (lastIndex >= 0 && values[lastIndex].value === OMIT) {
|
||||
lastIndex -= 1;
|
||||
}
|
||||
|
||||
const args = [];
|
||||
for (let index = 0; index <= lastIndex; index += 1) {
|
||||
const entry = values[index];
|
||||
args.push(entry.value === OMIT ? entry.placeholder : entry.value);
|
||||
}
|
||||
|
||||
const call = args.length ? `map(${args.join(", ")})` : "map()";
|
||||
const line = `${indent(indentLevel)}${call}`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
41
scripts/nodes/library/map/mget.js
Normal file
41
scripts/nodes/library/map/mget.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Reads a map tile value using PICO-8's mget helper.
|
||||
*/
|
||||
export const mapMgetNode = createNodeModule(
|
||||
{
|
||||
id: "map_mget",
|
||||
title: "Get Map Tile",
|
||||
category: "Map",
|
||||
description: "Fetch the numeric map value at a tile coordinate.",
|
||||
searchTags: ["mget", "map", "tile", "read"],
|
||||
inputs: [
|
||||
{
|
||||
id: "x",
|
||||
name: "Tile X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Tile Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Value", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
return `mget(${x}, ${y})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
51
scripts/nodes/library/map/mset.js
Normal file
51
scripts/nodes/library/map/mset.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Writes a map tile value using PICO-8's mset helper.
|
||||
*/
|
||||
export const mapMsetNode = createNodeModule(
|
||||
{
|
||||
id: "map_mset",
|
||||
title: "Set Map Tile",
|
||||
category: "Map",
|
||||
description: "Store a numeric value at a tile coordinate.",
|
||||
searchTags: ["mset", "map", "tile", "write"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x",
|
||||
name: "Tile X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "y",
|
||||
name: "Tile Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "value",
|
||||
name: "Value",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const x = resolveValueInput("x", "0");
|
||||
const y = resolveValueInput("y", "0");
|
||||
const value = resolveValueInput("value", "0");
|
||||
const line = `${indent(indentLevel)}mset(${x}, ${y}, ${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
157
scripts/nodes/library/map/tline.js
Normal file
157
scripts/nodes/library/map/tline.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Draws textured lines or configures precision using PICO-8's tline helper.
|
||||
*/
|
||||
export const mapTlineNode = createNodeModule(
|
||||
{
|
||||
id: "map_tline",
|
||||
title: "Draw Textured Line",
|
||||
category: "Map",
|
||||
description:
|
||||
"Render a textured line from map data or adjust tline precision.",
|
||||
searchTags: ["tline", "map", "texture", "line"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "x0",
|
||||
name: "X0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Line start X",
|
||||
},
|
||||
{
|
||||
id: "y0",
|
||||
name: "Y0",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Line start Y",
|
||||
},
|
||||
{
|
||||
id: "x1",
|
||||
name: "X1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Line end X",
|
||||
},
|
||||
{
|
||||
id: "y1",
|
||||
name: "Y1",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Line end Y",
|
||||
},
|
||||
{
|
||||
id: "mx",
|
||||
name: "Map X",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Map sample X (tiles)",
|
||||
},
|
||||
{
|
||||
id: "my",
|
||||
name: "Map Y",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Map sample Y (tiles)",
|
||||
},
|
||||
{
|
||||
id: "mdx",
|
||||
name: "MDX",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Map X delta",
|
||||
},
|
||||
{
|
||||
id: "mdy",
|
||||
name: "MDY",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Map Y delta",
|
||||
},
|
||||
{
|
||||
id: "layers",
|
||||
name: "Layers",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Sprite flag bitmask",
|
||||
},
|
||||
{
|
||||
id: "precision",
|
||||
name: "Precision",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
description: "Precision override when setting mode",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
key: "mode",
|
||||
label: "Mode",
|
||||
type: "enum",
|
||||
defaultValue: "draw",
|
||||
options: [
|
||||
{ label: "Draw Line", value: "draw" },
|
||||
{ label: "Set Precision", value: "precision" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
emitExec: ({
|
||||
node,
|
||||
indent,
|
||||
indentLevel,
|
||||
resolveValueInput,
|
||||
emitNextExec,
|
||||
}) => {
|
||||
const mode = String(node.properties.mode ?? "draw");
|
||||
if (mode === "precision") {
|
||||
const precision = resolveValueInput("precision", "16");
|
||||
const line = `${indent(indentLevel)}tline(${precision})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
}
|
||||
|
||||
const OMIT = "__pg_omit__";
|
||||
const x0 = resolveValueInput("x0", OMIT);
|
||||
const y0 = resolveValueInput("y0", OMIT);
|
||||
const x1 = resolveValueInput("x1", OMIT);
|
||||
const y1 = resolveValueInput("y1", OMIT);
|
||||
const mx = resolveValueInput("mx", OMIT);
|
||||
const my = resolveValueInput("my", OMIT);
|
||||
const mdx = resolveValueInput("mdx", OMIT);
|
||||
const mdy = resolveValueInput("mdy", OMIT);
|
||||
const layers = resolveValueInput("layers", OMIT);
|
||||
|
||||
const values = [
|
||||
{ value: x0, placeholder: "0" },
|
||||
{ value: y0, placeholder: "0" },
|
||||
{ value: x1, placeholder: "0" },
|
||||
{ value: y1, placeholder: "0" },
|
||||
{ value: mx, placeholder: "0" },
|
||||
{ value: my, placeholder: "0" },
|
||||
{ value: mdx, placeholder: "0.125" },
|
||||
{ value: mdy, placeholder: "0" },
|
||||
{ value: layers, placeholder: "nil" },
|
||||
];
|
||||
|
||||
let lastIndex = values.length - 1;
|
||||
while (lastIndex >= 0 && values[lastIndex].value === OMIT) {
|
||||
lastIndex -= 1;
|
||||
}
|
||||
|
||||
const args = [];
|
||||
for (let index = 0; index <= lastIndex; index += 1) {
|
||||
const entry = values[index];
|
||||
args.push(entry.value === OMIT ? entry.placeholder : entry.value);
|
||||
}
|
||||
|
||||
const call = args.length ? `tline(${args.join(", ")})` : "tline()";
|
||||
const line = `${indent(indentLevel)}${call}`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/abs.js
Normal file
25
scripts/nodes/library/math/abs.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Computes the absolute value using abs.
|
||||
*/
|
||||
export const mathAbsNode = createNodeModule(
|
||||
{
|
||||
id: "math_abs",
|
||||
title: "Abs",
|
||||
category: "Math",
|
||||
description: "Return the absolute value of a number.",
|
||||
searchTags: ["abs", "absolute", "math"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `abs(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
30
scripts/nodes/library/math/atan2.js
Normal file
30
scripts/nodes/library/math/atan2.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Computes an angle from delta components using atan2.
|
||||
*/
|
||||
export const mathAtan2Node = createNodeModule(
|
||||
{
|
||||
id: "math_atan2",
|
||||
title: "Atan2",
|
||||
category: "Math",
|
||||
description:
|
||||
"Return the screenspace angle for a delta X and Y pair.",
|
||||
searchTags: ["atan2", "angle", "math", "trig"],
|
||||
inputs: [
|
||||
{ id: "dx", name: "DX", direction: "input", kind: "number" },
|
||||
{ id: "dy", name: "DY", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const dx = resolveValueInput("dx", "0");
|
||||
const dy = resolveValueInput("dy", "0");
|
||||
return `atan2(${dx}, ${dy})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/band.js
Normal file
29
scripts/nodes/library/math/band.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs bitwise and using band.
|
||||
*/
|
||||
export const mathBandNode = createNodeModule(
|
||||
{
|
||||
id: "math_band",
|
||||
title: "Bitwise AND",
|
||||
category: "Math",
|
||||
description: "Return bits set in both operands using BAND().",
|
||||
searchTags: ["band", "bitwise", "and", "math"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `band(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/bnot.js
Normal file
25
scripts/nodes/library/math/bnot.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs bitwise not using bnot.
|
||||
*/
|
||||
export const mathBnotNode = createNodeModule(
|
||||
{
|
||||
id: "math_bnot",
|
||||
title: "Bitwise NOT",
|
||||
category: "Math",
|
||||
description: "Invert every bit in a value using BNOT().",
|
||||
searchTags: ["bnot", "bitwise", "not", "math"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `bnot(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/bor.js
Normal file
29
scripts/nodes/library/math/bor.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs bitwise or using bor.
|
||||
*/
|
||||
export const mathBorNode = createNodeModule(
|
||||
{
|
||||
id: "math_bor",
|
||||
title: "Bitwise OR",
|
||||
category: "Math",
|
||||
description: "Return bits set in either operand using BOR().",
|
||||
searchTags: ["bor", "bitwise", "or", "math"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `bor(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/bxor.js
Normal file
29
scripts/nodes/library/math/bxor.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs bitwise exclusive-or using bxor.
|
||||
*/
|
||||
export const mathBxorNode = createNodeModule(
|
||||
{
|
||||
id: "math_bxor",
|
||||
title: "Bitwise XOR",
|
||||
category: "Math",
|
||||
description: "Return bits set in exactly one operand using BXOR().",
|
||||
searchTags: ["bxor", "bitwise", "xor", "math"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `bxor(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/ceil.js
Normal file
25
scripts/nodes/library/math/ceil.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Ceils a numeric value using ceil.
|
||||
*/
|
||||
export const mathCeilNode = createNodeModule(
|
||||
{
|
||||
id: "math_ceil",
|
||||
title: "Ceil",
|
||||
category: "Math",
|
||||
description: "Round a value up to the nearest integer.",
|
||||
searchTags: ["ceil", "ceiling", "math", "round"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `ceil(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
26
scripts/nodes/library/math/cos.js
Normal file
26
scripts/nodes/library/math/cos.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Computes the cosine of a value using cos.
|
||||
*/
|
||||
export const mathCosNode = createNodeModule(
|
||||
{
|
||||
id: "math_cos",
|
||||
title: "Cos",
|
||||
category: "Math",
|
||||
description:
|
||||
"Return the cosine of an angle where 1.0 represents a full turn.",
|
||||
searchTags: ["cos", "cosine", "math", "trig"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `cos(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/div.js
Normal file
29
scripts/nodes/library/math/div.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs integer division using the floor operation.
|
||||
*/
|
||||
export const mathDivNode = createNodeModule(
|
||||
{
|
||||
id: "math_div",
|
||||
title: "Integer Divide",
|
||||
category: "Math",
|
||||
description: "Return the floored quotient of two numbers (A \\ B).",
|
||||
searchTags: ["div", "integer", "divide", "math"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "1");
|
||||
return `(${a}) \\ (${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/flr.js
Normal file
25
scripts/nodes/library/math/flr.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Floors a numeric value using flr.
|
||||
*/
|
||||
export const mathFlrNode = createNodeModule(
|
||||
{
|
||||
id: "math_flr",
|
||||
title: "Floor",
|
||||
category: "Math",
|
||||
description: "Round a value down to the nearest integer.",
|
||||
searchTags: ["flr", "floor", "math", "round"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `flr(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
79
scripts/nodes/library/math/index.js
Normal file
79
scripts/nodes/library/math/index.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { mathMaxNode } from "./max.js";
|
||||
import { mathMinNode } from "./min.js";
|
||||
import { mathMidNode } from "./mid.js";
|
||||
import { mathFlrNode } from "./flr.js";
|
||||
import { mathCeilNode } from "./ceil.js";
|
||||
import { mathCosNode } from "./cos.js";
|
||||
import { mathSinNode } from "./sin.js";
|
||||
import { mathAtan2Node } from "./atan2.js";
|
||||
import { mathSqrtNode } from "./sqrt.js";
|
||||
import { mathAbsNode } from "./abs.js";
|
||||
import { mathRndNode } from "./rnd.js";
|
||||
import { mathSrandNode } from "./srand.js";
|
||||
import { mathBandNode } from "./band.js";
|
||||
import { mathBorNode } from "./bor.js";
|
||||
import { mathBxorNode } from "./bxor.js";
|
||||
import { mathBnotNode } from "./bnot.js";
|
||||
import { mathShlNode } from "./shl.js";
|
||||
import { mathShrNode } from "./shr.js";
|
||||
import { mathLshrNode } from "./lshr.js";
|
||||
import { mathRotlNode } from "./rotl.js";
|
||||
import { mathRotrNode } from "./rotr.js";
|
||||
import { mathDivNode } from "./div.js";
|
||||
|
||||
/**
|
||||
* All math-related node modules backed by PICO-8 helpers.
|
||||
*/
|
||||
export const mathNodes = [
|
||||
mathMaxNode,
|
||||
mathMinNode,
|
||||
mathMidNode,
|
||||
mathFlrNode,
|
||||
mathCeilNode,
|
||||
mathCosNode,
|
||||
mathSinNode,
|
||||
mathAtan2Node,
|
||||
mathSqrtNode,
|
||||
mathAbsNode,
|
||||
mathRndNode,
|
||||
mathSrandNode,
|
||||
mathBandNode,
|
||||
mathBorNode,
|
||||
mathBxorNode,
|
||||
mathBnotNode,
|
||||
mathShlNode,
|
||||
mathShrNode,
|
||||
mathLshrNode,
|
||||
mathRotlNode,
|
||||
mathRotrNode,
|
||||
mathDivNode,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checklist tracking coverage of documented math helpers.
|
||||
* @type {Array<{ function: string, nodeId: string, implemented: boolean }>}
|
||||
*/
|
||||
export const mathFunctionChecklist = [
|
||||
{ function: "MAX", nodeId: "math_max", implemented: true },
|
||||
{ function: "MIN", nodeId: "math_min", implemented: true },
|
||||
{ function: "MID", nodeId: "math_mid", implemented: true },
|
||||
{ function: "FLR", nodeId: "math_flr", implemented: true },
|
||||
{ function: "CEIL", nodeId: "math_ceil", implemented: true },
|
||||
{ function: "COS", nodeId: "math_cos", implemented: true },
|
||||
{ function: "SIN", nodeId: "math_sin", implemented: true },
|
||||
{ function: "ATAN2", nodeId: "math_atan2", implemented: true },
|
||||
{ function: "SQRT", nodeId: "math_sqrt", implemented: true },
|
||||
{ function: "ABS", nodeId: "math_abs", implemented: true },
|
||||
{ function: "RND", nodeId: "math_rnd", implemented: true },
|
||||
{ function: "SRAND", nodeId: "math_srand", implemented: true },
|
||||
{ function: "BAND", nodeId: "math_band", implemented: true },
|
||||
{ function: "BOR", nodeId: "math_bor", implemented: true },
|
||||
{ function: "BXOR", nodeId: "math_bxor", implemented: true },
|
||||
{ function: "BNOT", nodeId: "math_bnot", implemented: true },
|
||||
{ function: "SHL", nodeId: "math_shl", implemented: true },
|
||||
{ function: "SHR", nodeId: "math_shr", implemented: true },
|
||||
{ function: "LSHR", nodeId: "math_lshr", implemented: true },
|
||||
{ function: "ROTL", nodeId: "math_rotl", implemented: true },
|
||||
{ function: "ROTR", nodeId: "math_rotr", implemented: true },
|
||||
{ function: "\\", nodeId: "math_div", implemented: true },
|
||||
];
|
||||
29
scripts/nodes/library/math/lshr.js
Normal file
29
scripts/nodes/library/math/lshr.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs a logical right bit shift using lshr.
|
||||
*/
|
||||
export const mathLshrNode = createNodeModule(
|
||||
{
|
||||
id: "math_lshr",
|
||||
title: "Logical Shift Right",
|
||||
category: "Math",
|
||||
description: "Shift bits right with zeros entering from the left.",
|
||||
searchTags: ["lshr", "shift", "bitwise", "math"],
|
||||
inputs: [
|
||||
{ id: "value", name: "Value", direction: "input", kind: "number" },
|
||||
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const amount = resolveValueInput("amount", "0");
|
||||
return `lshr(${value}, ${amount})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/max.js
Normal file
29
scripts/nodes/library/math/max.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Returns the maximum of two values using max.
|
||||
*/
|
||||
export const mathMaxNode = createNodeModule(
|
||||
{
|
||||
id: "math_max",
|
||||
title: "Max",
|
||||
category: "Math",
|
||||
description: "Return the greater of two numeric values.",
|
||||
searchTags: ["max", "maximum", "math", "compare"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `max(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
31
scripts/nodes/library/math/mid.js
Normal file
31
scripts/nodes/library/math/mid.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Returns the median of three values using mid.
|
||||
*/
|
||||
export const mathMidNode = createNodeModule(
|
||||
{
|
||||
id: "math_mid",
|
||||
title: "Mid",
|
||||
category: "Math",
|
||||
description: "Return the middle value from three numeric inputs.",
|
||||
searchTags: ["mid", "median", "math", "compare"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
{ id: "c", name: "C", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
const c = resolveValueInput("c", "0");
|
||||
return `mid(${a}, ${b}, ${c})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/min.js
Normal file
29
scripts/nodes/library/math/min.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Returns the minimum of two values using min.
|
||||
*/
|
||||
export const mathMinNode = createNodeModule(
|
||||
{
|
||||
id: "math_min",
|
||||
title: "Min",
|
||||
category: "Math",
|
||||
description: "Return the lesser of two numeric values.",
|
||||
searchTags: ["min", "minimum", "math", "compare"],
|
||||
inputs: [
|
||||
{ id: "a", name: "A", direction: "input", kind: "number" },
|
||||
{ id: "b", name: "B", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "value", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const a = resolveValueInput("a", "0");
|
||||
const b = resolveValueInput("b", "0");
|
||||
return `min(${a}, ${b})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/rnd.js
Normal file
25
scripts/nodes/library/math/rnd.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Generates random numbers using rnd.
|
||||
*/
|
||||
export const mathRndNode = createNodeModule(
|
||||
{
|
||||
id: "math_rnd",
|
||||
title: "Random",
|
||||
category: "Math",
|
||||
description: "Return a random number in the range [0, x).",
|
||||
searchTags: ["rnd", "random", "math"],
|
||||
inputs: [{ id: "value", name: "Range", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "1");
|
||||
return `rnd(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/rotl.js
Normal file
29
scripts/nodes/library/math/rotl.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs a left bit rotation using rotl.
|
||||
*/
|
||||
export const mathRotlNode = createNodeModule(
|
||||
{
|
||||
id: "math_rotl",
|
||||
title: "Rotate Left",
|
||||
category: "Math",
|
||||
description: "Rotate bits left by a given amount.",
|
||||
searchTags: ["rotl", "rotate", "bitwise", "math"],
|
||||
inputs: [
|
||||
{ id: "value", name: "Value", direction: "input", kind: "number" },
|
||||
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const amount = resolveValueInput("amount", "0");
|
||||
return `rotl(${value}, ${amount})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/rotr.js
Normal file
29
scripts/nodes/library/math/rotr.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs a right bit rotation using rotr.
|
||||
*/
|
||||
export const mathRotrNode = createNodeModule(
|
||||
{
|
||||
id: "math_rotr",
|
||||
title: "Rotate Right",
|
||||
category: "Math",
|
||||
description: "Rotate bits right by a given amount.",
|
||||
searchTags: ["rotr", "rotate", "bitwise", "math"],
|
||||
inputs: [
|
||||
{ id: "value", name: "Value", direction: "input", kind: "number" },
|
||||
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const amount = resolveValueInput("amount", "0");
|
||||
return `rotr(${value}, ${amount})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/shl.js
Normal file
29
scripts/nodes/library/math/shl.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs a left bit shift using shl.
|
||||
*/
|
||||
export const mathShlNode = createNodeModule(
|
||||
{
|
||||
id: "math_shl",
|
||||
title: "Shift Left",
|
||||
category: "Math",
|
||||
description: "Shift bits left with zeros entering from the right.",
|
||||
searchTags: ["shl", "shift", "bitwise", "math"],
|
||||
inputs: [
|
||||
{ id: "value", name: "Value", direction: "input", kind: "number" },
|
||||
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const amount = resolveValueInput("amount", "0");
|
||||
return `shl(${value}, ${amount})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/shr.js
Normal file
29
scripts/nodes/library/math/shr.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Performs an arithmetic right bit shift using shr.
|
||||
*/
|
||||
export const mathShrNode = createNodeModule(
|
||||
{
|
||||
id: "math_shr",
|
||||
title: "Shift Right",
|
||||
category: "Math",
|
||||
description: "Shift bits right while extending the sign bit.",
|
||||
searchTags: ["shr", "shift", "bitwise", "math"],
|
||||
inputs: [
|
||||
{ id: "value", name: "Value", direction: "input", kind: "number" },
|
||||
{ id: "amount", name: "Amount", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const amount = resolveValueInput("amount", "0");
|
||||
return `shr(${value}, ${amount})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
26
scripts/nodes/library/math/sin.js
Normal file
26
scripts/nodes/library/math/sin.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Computes the sine of a value using sin.
|
||||
*/
|
||||
export const mathSinNode = createNodeModule(
|
||||
{
|
||||
id: "math_sin",
|
||||
title: "Sin",
|
||||
category: "Math",
|
||||
description:
|
||||
"Return the sine of an angle where 1.0 represents a full turn.",
|
||||
searchTags: ["sin", "sine", "math", "trig"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `sin(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
25
scripts/nodes/library/math/sqrt.js
Normal file
25
scripts/nodes/library/math/sqrt.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Computes a square root using sqrt.
|
||||
*/
|
||||
export const mathSqrtNode = createNodeModule(
|
||||
{
|
||||
id: "math_sqrt",
|
||||
title: "Sqrt",
|
||||
category: "Math",
|
||||
description: "Return the square root of a numeric value.",
|
||||
searchTags: ["sqrt", "square root", "math"],
|
||||
inputs: [{ id: "value", name: "Value", direction: "input", kind: "number" }],
|
||||
outputs: [
|
||||
{ id: "result", name: "Result", direction: "output", kind: "number" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
evaluateValue: ({ resolveValueInput }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
return `sqrt(${value})`;
|
||||
},
|
||||
}
|
||||
);
|
||||
29
scripts/nodes/library/math/srand.js
Normal file
29
scripts/nodes/library/math/srand.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Seeds the random number generator using srand.
|
||||
*/
|
||||
export const mathSrandNode = createNodeModule(
|
||||
{
|
||||
id: "math_srand",
|
||||
title: "Seed Random",
|
||||
category: "Math",
|
||||
description: "Set the random number generator seed.",
|
||||
searchTags: ["srand", "random", "seed", "math"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{ id: "value", name: "Seed", direction: "input", kind: "number" },
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const value = resolveValueInput("value", "0");
|
||||
const line = `${indent(indentLevel)}srand(${value})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
67
scripts/nodes/library/memory/cstore.js
Normal file
67
scripts/nodes/library/memory/cstore.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { createNodeModule } from "../../nodeTypes.js";
|
||||
|
||||
/**
|
||||
* Copies data from base RAM to cartridge ROM using cstore.
|
||||
*/
|
||||
export const memoryCstoreNode = createNodeModule(
|
||||
{
|
||||
id: "memory_cstore",
|
||||
title: "Store To Cart",
|
||||
category: "Memory",
|
||||
description:
|
||||
"Copy base RAM into cart ROM, optionally targeting another cartridge.",
|
||||
searchTags: ["cstore", "memory", "cart", "save"],
|
||||
inputs: [
|
||||
{ id: "exec_in", name: "Exec", direction: "input", kind: "exec" },
|
||||
{
|
||||
id: "dest",
|
||||
name: "Dest",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "source",
|
||||
name: "Source",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "length",
|
||||
name: "Length",
|
||||
direction: "input",
|
||||
kind: "number",
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
id: "filename",
|
||||
name: "Filename",
|
||||
direction: "input",
|
||||
kind: "string",
|
||||
description: "Optional cartridge filename",
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
{ id: "exec_out", name: "Exec", direction: "output", kind: "exec" },
|
||||
],
|
||||
properties: [],
|
||||
},
|
||||
{
|
||||
emitExec: ({ indent, indentLevel, resolveValueInput, emitNextExec }) => {
|
||||
const OMIT = "__pg_omit__";
|
||||
const dest = resolveValueInput("dest", "0");
|
||||
const source = resolveValueInput("source", "0");
|
||||
const length = resolveValueInput("length", "0");
|
||||
const filename = resolveValueInput("filename", OMIT);
|
||||
|
||||
const args = [dest, source, length];
|
||||
if (filename !== OMIT) {
|
||||
args.push(filename);
|
||||
}
|
||||
|
||||
const line = `${indent(indentLevel)}cstore(${args.join(", ")})`;
|
||||
return [line, ...emitNextExec("exec_out")];
|
||||
},
|
||||
}
|
||||
);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user