594 lines
13 KiB
JavaScript
594 lines
13 KiB
JavaScript
"use math";
|
|
var files = {};
|
|
function load(file) {
|
|
var modtime = cmd(0, file);
|
|
files[file] = modtime;
|
|
}
|
|
|
|
var cmd_args = function(cc)
|
|
{
|
|
console.warn(cc);
|
|
}
|
|
|
|
load("scripts/base.js");
|
|
load("scripts/std.js");
|
|
//load("scripts/lunr.js");
|
|
//var lunrtxt = load("scripts/lunr.js");
|
|
//eval(lunrtxt);
|
|
|
|
/* The global namespace file */
|
|
var prosp = {};
|
|
|
|
function run(file)
|
|
{
|
|
var modtime = cmd(119, file);
|
|
if (modtime === 0) {
|
|
Log.stack();
|
|
return false;
|
|
}
|
|
|
|
files[file] = modtime;
|
|
return cmd(117, file);
|
|
}
|
|
|
|
run.doc = `Load a given script file.`;
|
|
load.doc = `Load a given script file.`;
|
|
|
|
function run_env(file, env)
|
|
{
|
|
var script = IO.slurp(file);
|
|
return function(){return eval(script);}.call(env);
|
|
}
|
|
|
|
run_env.doc = `Load a given script file, evaluating it in the context of the object 'env'.`;
|
|
|
|
load("scripts/diff.js");
|
|
Log.level = 1;
|
|
|
|
load("scripts/color.js");
|
|
|
|
function bb2wh(bb) {
|
|
return [bb.r-bb.l, bb.t-bb.b];
|
|
};
|
|
|
|
var Device = {
|
|
pc: [1920,1080],
|
|
macbook_m2: [2560,1664, 13.6],
|
|
ds_top: [400,240, 3.53],
|
|
ds_bottom: [320,240, 3.02],
|
|
playdate: [400,240,2.7],
|
|
switch: [1280,720, 6.2],
|
|
switch_lite: [1280,720,5.5],
|
|
switch_oled: [1280,720,7],
|
|
dsi: [256,192,3.268],
|
|
ds: [256,192, 3],
|
|
dsixl: [256,192,4.2],
|
|
ipad_air_m2: [2360,1640, 11.97],
|
|
iphone_se: [1334, 750, 4.7],
|
|
iphone_12_pro: [2532,1170,6.06],
|
|
iphone_15: [2556,1179,6.1],
|
|
gba: [240,160,2.9],
|
|
gameboy: [160,144,2.48],
|
|
gbc: [160,144,2.28],
|
|
steamdeck: [1280,800,7],
|
|
vita: [960,544,5],
|
|
psp: [480,272,4.3],
|
|
imac_m3: [4480,2520,23.5],
|
|
macbook_pro_m3: [3024,1964, 14.2],
|
|
ps1: [320,240,5],
|
|
ps2: [640,480],
|
|
snes: [256,224],
|
|
gamecube: [640,480],
|
|
n64: [320,240],
|
|
c64: [320,200],
|
|
macintosh: [512,342,9],
|
|
gamegear: [160,144,3.2],
|
|
};
|
|
|
|
Device.doc = `Device resolutions given as [x,y,inches diagonal].`;
|
|
|
|
var prosperon = {};
|
|
prosperon.version = cmd(255);
|
|
prosperon.revision = cmd(256);
|
|
|
|
prosperon.semver = {};
|
|
prosperon.semver.valid = function(v, range)
|
|
{
|
|
v = v.split('.');
|
|
range = range.split('.');
|
|
if (v.length !== 3) return undefined;
|
|
if (range.length !== 3) return undefined;
|
|
|
|
if (range[0][0] === '^') {
|
|
range[0] = range[0].slice(1);
|
|
if (parseInt(v[0]) >= parseInt(range[0])) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
if (range[0] === '~') {
|
|
range[0] = range[0].slice(1);
|
|
for (var i = 0; i < 2; i++)
|
|
if (parseInt(v[i]) < parseInt(range[i])) return false;
|
|
return true;
|
|
}
|
|
|
|
return prosperon.semver.cmp(v.join('.'), range.join('.')) === 0;
|
|
}
|
|
|
|
prosperon.semver.cmp = function(v1, v2)
|
|
{
|
|
var ver1 = v1.split('.');
|
|
var ver2 = v2.split('.');
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
var n1 = parseInt(ver1[i]);
|
|
var n2 = parseInt(ver2[i]);
|
|
if (n1 > n2)
|
|
return 1;
|
|
else if (n1 < n2)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
prosperon.semver.doc = "Functions for semantic versioning numbers. Semantic versioning is given as a triple digit number, as MAJOR.MINOR.PATCH.";
|
|
prosperon.semver.cmp.doc = "Compare two semantic version numbers, given like X.X.X.";
|
|
prosperon.semver.valid.doc = `Test if semantic version v is valid, given a range.
|
|
Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^.
|
|
~ means that MAJOR and MINOR must match exactly, but any PATCH greater or equal is valid.
|
|
^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`;
|
|
|
|
|
|
load("scripts/gui.js");
|
|
|
|
var timer = {
|
|
update(dt) {
|
|
this.remain -= dt;
|
|
if (this.remain <= 0) {
|
|
this.fn();
|
|
this.kill();
|
|
}
|
|
},
|
|
|
|
kill() {
|
|
this.end();
|
|
this.fn = undefined;
|
|
},
|
|
|
|
delay(fn, secs) {
|
|
var t = Object.create(this);
|
|
t.time = secs;
|
|
t.remain = secs;
|
|
t.fn = fn;
|
|
t.end = Register.update.register(timer.update.bind(t));
|
|
return function() { t.kill(); };
|
|
},
|
|
};
|
|
|
|
load("scripts/tween.js");
|
|
|
|
var Render = {
|
|
normal() { cmd(67);},
|
|
wireframe() { cmd(68); },
|
|
|
|
pass() {
|
|
|
|
},
|
|
};
|
|
|
|
Render.doc = {
|
|
doc: "Functions for rendering modes.",
|
|
normal: "Final render with all lighting.",
|
|
wireframe: "Show only wireframes of models."
|
|
};
|
|
|
|
load("scripts/physics.js");
|
|
load("scripts/input.js");
|
|
load("scripts/sound.js");
|
|
load("scripts/ai.js");
|
|
load("scripts/geometry.js");
|
|
|
|
function screen2world(screenpos) {
|
|
if (Game.camera)
|
|
return Game.camera.view2world(screenpos);
|
|
return screenpos;
|
|
}
|
|
function world2screen(worldpos) { return Game.camera.world2view(worldpos); }
|
|
|
|
var Register = {
|
|
kbm_input(mode, btn, state, ...args) {
|
|
if (state === 'released') {
|
|
btn = btn.split('-').last();
|
|
}
|
|
|
|
switch(mode) {
|
|
case "emacs":
|
|
Player.players[0].raw_input(btn, state, ...args);
|
|
break;
|
|
|
|
case "mouse":
|
|
Player.players[0].mouse_input(btn, state, ...args);
|
|
break;
|
|
|
|
case "char":
|
|
Player.players[0].char_input(btn);
|
|
break;
|
|
};
|
|
},
|
|
|
|
gamepad_playermap: [],
|
|
gamepad_input(pad, btn, state, ...args) {
|
|
var player = this.gamepad_playermap[pad];
|
|
if (!player) return;
|
|
|
|
var statestr = Input.state2str(state);
|
|
|
|
var rawfn = `gamepad_${btn}_${statestr}`;
|
|
player.input(rawfn, ...args);
|
|
|
|
Action.actions.forEach(x => {
|
|
if (x.inputs.includes(btn))
|
|
player.input(`action_${x.name}_${statestr}`, ...args);
|
|
});
|
|
},
|
|
|
|
unregister_obj(obj) { Player.uncontrol(obj); },
|
|
|
|
endofloop(fn) {
|
|
if (!this.inloop)
|
|
fn();
|
|
else {
|
|
this.loopcbs.push(fn);
|
|
}
|
|
},
|
|
|
|
clear() {
|
|
Register.registries.forEach(function(n) {
|
|
n.entries = [];
|
|
});
|
|
},
|
|
|
|
registries: [],
|
|
|
|
add_cb(idx, name) {
|
|
var n = {};
|
|
var fns = [];
|
|
|
|
n.register = function(fn, obj) {
|
|
if (typeof fn !== 'function') return;
|
|
if (typeof obj === 'object')
|
|
fn = fn.bind(obj);
|
|
fns.push(fn);
|
|
return function() { fns.remove(fn); };
|
|
}
|
|
n.broadcast = function(...args) { fns.forEach(x => x(...args)); }
|
|
n.clear = function() { fns = []; }
|
|
|
|
register(idx, n.broadcast, n);
|
|
|
|
Register[name] = n;
|
|
Register.registries.push(n);
|
|
|
|
return n;
|
|
},
|
|
};
|
|
|
|
Register.add_cb(0, "update").doc = "Called once per frame.";
|
|
Register.add_cb(11, "appupdate");
|
|
Register.add_cb(1, "physupdate");
|
|
Register.add_cb(2, "gui");
|
|
Register.add_cb(6, "debug");
|
|
register(7, Register.kbm_input, Register);
|
|
Register.add_cb(8, "gamepad_input");
|
|
Register.add_cb(10, "draw");
|
|
|
|
register(9, Log.stack, this);
|
|
|
|
Register.gamepad_playermap[0] = Player.players[0];
|
|
|
|
var Signal = {
|
|
obj_begin(fn, go) {
|
|
register_collide(0, fn, go.body);
|
|
},
|
|
|
|
obj_separate(fn, go) {
|
|
register_collide(3,fn, go.body);
|
|
},
|
|
};
|
|
|
|
var Event = {
|
|
events: {},
|
|
|
|
observe(name, obj, fn) {
|
|
this.events[name] ??= [];
|
|
this.events[name].push([obj, fn]);
|
|
},
|
|
|
|
unobserve(name, obj) {
|
|
this.events[name] = this.events[name].filter(x => x[0] !== obj);
|
|
},
|
|
|
|
rm_obj(obj) {
|
|
Object.keys(this.events).forEach(name => Event.unobserve(name,obj));
|
|
},
|
|
|
|
notify(name) {
|
|
if (!this.events[name]) return;
|
|
this.events[name].forEach(function(x) {
|
|
x[1].call(x[0]);
|
|
});
|
|
},
|
|
};
|
|
|
|
var Window = {
|
|
fullscreen(f) { cmd(145, f); },
|
|
set width(w) { cmd(125, w); },
|
|
set height(h) { cmd(126, h); },
|
|
get width() { return cmd(48); },
|
|
get height() { return cmd(49); },
|
|
get dimensions() { return [this.width, this.height]; },
|
|
set name(str) { cmd(134, str); },
|
|
boundingbox() {
|
|
return {
|
|
t: Window.height,
|
|
b: 0,
|
|
r: Window.width,
|
|
l: 0
|
|
};
|
|
},
|
|
};
|
|
|
|
Window.icon = function(path) { cmd(90, path); };
|
|
Window.icon.doc = "Set the icon of the window using the PNG image at path.";
|
|
|
|
function reloadfiles() {
|
|
Object.keys(files).forEach(function (x) { load(x); });
|
|
}
|
|
|
|
load("scripts/debug.js");
|
|
|
|
/*
|
|
function Color(from) {
|
|
var color = Object.create(Array);
|
|
Object.defineProperty(color, 'r', setelem(0));
|
|
Object.defineProperty(color, 'g', setelem(1));
|
|
Object.defineProperty(color, 'b', setelem(2));
|
|
Object.defineProperty(color, 'a', setelem(3));
|
|
|
|
color.a = color.g = color.b = color.a = 1;
|
|
Object.assign(color, from);
|
|
|
|
return color;
|
|
};
|
|
*/
|
|
|
|
var Spline = {};
|
|
Spline.sample_angle = function(type, points, angle) {
|
|
return spline_cmd(0, type, points[0].length, points, angle);
|
|
}
|
|
|
|
Spline.bezier_loop = function(cp)
|
|
{
|
|
cp.push(Vector.reflect_point(cp.at(-2),cp.at(-1)));
|
|
cp.push(Vector.reflect_point(cp[1],cp[0]));
|
|
cp.push(cp[0].slice());
|
|
return cp;
|
|
}
|
|
|
|
Spline.bezier_node_count = function(cp)
|
|
{
|
|
if (cp.length === 4) return 2;
|
|
return 2 + (cp.length-4)/3;
|
|
}
|
|
|
|
Spline.is_bezier = function(t) { return t === Spline.type.bezier; }
|
|
Spline.is_catmull = function(t) { return t === Spline.type.catmull; }
|
|
|
|
Spline.bezier2catmull = function(b)
|
|
{
|
|
var c = [];
|
|
for (var i = 0; i < b.length; i += 3)
|
|
c.push(b[i]);
|
|
return c;
|
|
}
|
|
|
|
Spline.catmull2bezier = function(c)
|
|
{
|
|
var b = [];
|
|
for (var i = 1; i < c.length-2; i++) {
|
|
b.push(c[i].slice());
|
|
b.push(c[i+1].sub(c[i-1]).scale(0.25).add(c[i]));
|
|
b.push(c[i].sub(c[i+2]).scale(0.25).add(c[i+1]));
|
|
}
|
|
b.push(c[c.length-2]);
|
|
return b;
|
|
}
|
|
|
|
Spline.catmull_loop = function(cp)
|
|
{
|
|
cp = cp.slice();
|
|
cp.unshift(cp.last());
|
|
cp.push(cp[1]);
|
|
cp.push(cp[2]);
|
|
return cp;
|
|
}
|
|
|
|
Spline.catmull_caps = function(cp)
|
|
{
|
|
cp = cp.slice();
|
|
cp.unshift(cp[0].sub(cp[1]).add(cp[0]));
|
|
cp.push(cp.last().sub(cp.at(-2).add(cp.last())));
|
|
return cp;
|
|
}
|
|
|
|
Spline.catmull2bezier.doc = "Given a set of control points C for a camtull-rom type curve, return a set of cubic bezier points to give the same curve."
|
|
|
|
Spline.type = {
|
|
catmull: 0,
|
|
bezier: 1,
|
|
bspline: 2,
|
|
cubichermite: 3
|
|
};
|
|
|
|
Spline.bezier_tan_partner = function(points, i)
|
|
{
|
|
if (i%3 === 0) return undefined;
|
|
var partner_i = (i%3) === 2 ? i-1 : i+1;
|
|
return points[i];
|
|
}
|
|
|
|
Spline.bezier_cp_mirror = function(points, i)
|
|
{
|
|
if (i%3 === 0) return undefined;
|
|
var partner_i = (i%3) === 2 ? i+2 : i-2;
|
|
var node_i = (i%3) === 2 ? i+1 : i-1;
|
|
if (partner_i >= points.length || node_i >= points.length) return;
|
|
points[partner_i] = points[node_i].sub(points[i]).add(points[node_i]);
|
|
}
|
|
|
|
Spline.bezier_point_handles = function(points, i)
|
|
{
|
|
if (!Spline.bezier_is_node(points,i)) return [];
|
|
var a = i-1;
|
|
var b = i+1;
|
|
var c = []
|
|
if (a > 0)
|
|
c.push(a);
|
|
|
|
if (b < points.length)
|
|
c.push(b);
|
|
|
|
return c;
|
|
}
|
|
|
|
Spline.bezier_nodes = function(points)
|
|
{
|
|
var c = [];
|
|
for (var i = 0; i < points.length; i+=3)
|
|
c.push(points[i].slice());
|
|
|
|
return c;
|
|
}
|
|
|
|
Spline.bezier_is_node = function(points, i) { return i%3 === 0; }
|
|
Spline.bezier_is_handle = function(points, i) { return !Spline.bezier_is_node(points,i); }
|
|
|
|
load("scripts/components.js");
|
|
|
|
var Game = {
|
|
engine_start(fn) {
|
|
cmd(257, fn);
|
|
Sound.bus.master = cmd(180);
|
|
Sound.master = Sound.bus.master;
|
|
},
|
|
|
|
native: Device.pc,
|
|
|
|
object_count() {
|
|
return cmd(214);
|
|
},
|
|
|
|
all_objects(fn) {
|
|
/* Wind down from Primum */
|
|
},
|
|
|
|
/* Returns a list of objects by name */
|
|
find(name) {
|
|
|
|
},
|
|
|
|
/* Return a list of objects derived from a specific prototype */
|
|
find_proto(proto) {
|
|
|
|
},
|
|
|
|
/* List of all objects spawned that have a specific tag */
|
|
find_tag(tag){
|
|
|
|
},
|
|
|
|
quit() { sys_cmd(0); },
|
|
pause() { sys_cmd(3); },
|
|
stop() { Game.pause(); },
|
|
step() { sys_cmd(4);},
|
|
editor_mode(m) { sys_cmd(10, m); },
|
|
playing() { return sys_cmd(5); },
|
|
paused() { return sys_cmd(6); },
|
|
stepping() { return cmd(79); },
|
|
play() { sys_cmd(1); },
|
|
|
|
wait_fns: [],
|
|
|
|
wait_exec(fn) {
|
|
if (!phys_stepping())
|
|
fn();
|
|
else
|
|
this.wait_fns.push(fn);
|
|
},
|
|
|
|
exec() {
|
|
this.wait_fns.forEach(function(x) { x(); });
|
|
|
|
this.wait_fns = [];
|
|
},
|
|
};
|
|
|
|
Game.gc = function() { cmd(259); }
|
|
Game.gc.doc = "Force the garbage collector to run.";
|
|
|
|
Game.doc = {};
|
|
Game.doc.object = "Returns the entity belonging to a given id.";
|
|
Game.doc.quit = "Immediately quit the game.";
|
|
Game.doc.pause = "Pause game simulation.";
|
|
Game.doc.stop = "Stop game simulation. This does the same thing as 'pause', and if the game is a debug build, starts its editor.";
|
|
Game.doc.play = "Resume or start game simulation.";
|
|
Game.doc.editor_mode = "Set to true for the game to only update on input; otherwise the game updates every frame.";
|
|
Game.doc.dt = "Current frame dt.";
|
|
Game.doc.view_camera = "Set the camera for the current view.";
|
|
Game.doc.camera = "Current camera.";
|
|
|
|
Window.doc = {};
|
|
Window.doc.width = "Width of the game window.";
|
|
Window.doc.height = "Height of the game window.";
|
|
Window.doc.dimensions = "Window width and height packaged in an array [width,height]";
|
|
Window.doc.name = "Name in the title bar of the window.";
|
|
Window.doc.boundingbox = "Boundingbox of the window, with top and right being its height and width.";
|
|
|
|
Register.update.register(Game.exec, Game);
|
|
|
|
load("scripts/entity.js");
|
|
|
|
function world_start() {
|
|
globalThis.Primum = Object.create(gameobject);
|
|
Primum.objects = {};
|
|
Primum.check_dirty = function() {};
|
|
Primum.namestr = function(){};
|
|
Primum._ed = {
|
|
selectable:false,
|
|
dirty:false,
|
|
};
|
|
Primum.toString = function() { return "Primum"; };
|
|
Primum.ur = "Primum";
|
|
Primum.kill = function() { this.clear(); };
|
|
Primum.phys = 2;
|
|
|
|
gameobject.level = Primum;
|
|
gameobject.body = make_gameobject();
|
|
cmd(113,gameobject.body, gameobject);
|
|
Object.hide(gameobject, 'timescale');
|
|
}
|
|
|
|
load("scripts/physics.js");
|
|
|
|
Game.view_camera = function(cam)
|
|
{
|
|
Game.camera = cam;
|
|
cmd(61, Game.camera.body);
|
|
}
|
|
|
|
Window.name = "Prosperon (V0.1)";
|
|
Window.width = 1280;
|
|
Window.height = 720;
|