changed make to spawn; unified actor system for entities

This commit is contained in:
John Alanbrook 2024-02-29 19:54:33 +00:00
parent de74b42be2
commit 1e432346ff
25 changed files with 814 additions and 738 deletions

View file

@ -119,7 +119,6 @@ When an actor dies, all of the actors that have it as their master[fn::What a mo
*** Turns
Actors get fragments of time called a *turn*. Actors which belong to different systems can have different lengths of turns.
*** Actor files
Actor files end with the extension *.jso*[fn::"Javascript object".]. They list a series of functions to call on a newly formed actor. Actors have a number of useful functions which are called as defined.
@ -184,7 +183,11 @@ Game worlds are made of entities. Entities are a type of actor with a number of
The first and most masterful entity is the Primum. The Primum has no components, and its rotation and position are zero. It defines the center of the game.
#+end_scholium
In editor mode, when an entity moves, all of its *padawans* also move. When the game is actively simulating, this only holds if there are physical constraints between them.
In editor mode, when an entity moves, all of its *padawans* also move.
When the game is actively simulating, this only holds if there are physical constraints between them.
Prosperon automatically generates physical pin constraints between objects with the appropriate physical properties.
*** Adding Components
Entities can have *components*. Components are essentially javascript wrappers over C code into the engine. Scripting is done to set the components up on entities, after which most of the work is done by the C plugin.
@ -206,7 +209,7 @@ Components only work in the context of an entity. They have no meaning outside o
While components can be added via scripting, it is easier to add them via the editor, as we will later see.
*** Ur system
The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. Entities can be created from ur types using the *spawn* function.
The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. An *ur* represents a set of instructions to create the (text, config) needed to spawn an actor or entity.
#+begin_scholium
Create an ur from the *hello* files above, and then spawn it.
@ -217,11 +220,21 @@ Primum.spawn(ur.hello);
When creating an actor from source files, all of its setup must take place. In this example, the setup happens during *ur.create*, and spawning is simply a matter of prototyping it.
#+end_scholium
Each ur type has some useful fields.
Each ur has the following fields.
| field | description |
|-----------+----------------------------------|
| instances | An array of instances of this ur |
| field | description |
|-----------+-------------------------------------------------------------|
| instances | An array of instances of this ur |
| name | Name of the ur |
| text | Path to the script file |
| data | Object to write to a newly generated actor |
| proto | An object that looks like a freshly made entity from the ur |
An *ur* has a full path given like ~ur.goblin.big~. ~goblin~ and ~big~ can both possibly have a *.jso* script as well as a *data* file.
When ~goblin.big~ is created, the new object has the ~goblin~ script run on it, followed by the ~big~ script. The ~data~ fields consist of objects prototyped from each other, so that the ~__proto__~ of ~big.data~ is ~goblin.data~. All fields of this objects are assigned to the ~big goblin~.
The unaltered form of every ur-based-entity is saved in the ur's ~proto~ field. As you edit objects, the differences between how your object is now, compared to its ~ur.proto~ is a list of differences. These differences can be rolled into the ~ur~, or saved as a subtype.
*** Prototyping Entities
Ur types are the prototype of created entities. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. When a value is changed on an entity, it is private. When a value is changed on an ur, it propogates to all entities. Values cannot be added or removed in subtypes.

72
scripts/actor.js Normal file
View file

@ -0,0 +1,72 @@
var actor = {};
var a_db = {};
actor.spawn = function(script, config){
if (typeof script !== 'string') return undefined;
if (!a_db[script]) a_db[script] = io.slurp(script);
var padawan = Object.create(actor);
eval_env(a_db[script], padawan);
if (typeof config === 'object')
Object.merge(padawan,config);
padawan.padawans = [];
padawan.timers = [];
padawan.master = this;
Object.hide(padawan, "master","timers", "padawans");
this.padawans.push(padawan);
return padawan;
};
actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`;
actor.timers = [];
actor.kill = function(){
if (this.__dead__) return;
this.timers.forEach(t => t.kill());
if (this.master)
delete this.master[this.toString()];
this.padawans.forEach(p => p.kill());
this.padawans = [];
this.__dead__ = true;
if (typeof this.die === 'function') this.die();
if (typeof this.stop === 'function') this.stop();
};
actor.kill.doc = `Remove this actor and all its padawans from existence.`;
actor.delay = function(fn, seconds) {
var t = Object.create(timer);
t.remain = seconds;
t.kill = () => {
timer.kill.call(t);
delete this.timers[t.toString()];
}
t.fire = () => {
if (this.__dead__) return;
fn();
t.kill();
};
Register.appupdate.register(t.update, t);
this.timers.push(t);
return function() { t.kill(); };
};
actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`;
actor.padawans = [];
actor.remaster = function(to){
delete this.master.padawans[this.toString()];
this.master = to;
to.padawans.push(this);
};
global.app = Object.create(actor);
app.die = function()
{
Game.quit();
}
return {actor, app};

View file

@ -38,7 +38,7 @@ var ai = {
if (Vector.length(dir) < 10) return true;
this.velocity = Vector.norm(this.randomloc.sub(this.pos)).scale(20);
return false;
return False;
}
},
@ -61,3 +61,5 @@ var ai = {
};
},
};
return {ai};

View file

@ -465,6 +465,18 @@ Object.containingKey = function(obj, prop)
return o;
}
Object.access = function(obj, name)
{
var dig = name.split('.');
for (var i of dig) {
obj = obj[i];
if (!obj) return undefined;
}
return obj;
}
Object.isAccessor = function(obj, prop)
{
var o = Object.containingKey(obj,prop);
@ -490,11 +502,11 @@ Object.mergekey = function(o1,o2,k)
o1[k] = o2[k];
}
} else
Object.defineProperty(o1, k, Object.getOwnPropertyDescriptor(o2,k));
// o1[k] = o2[k];
o1[k] = o2[k];
}
/* Same as merge from Ruby */
/* Adds objs key by key to target */
Object.merge = function(target, ...objs)
{
for (var obj of objs)
@ -575,10 +587,12 @@ Object.defineProperty(Object.prototype, 'obscure', {
Object.defineProperty(Object.prototype, 'mixin', {
value: function(obj) {
if (typeof obj === 'string') {
obj = use(obj);
if (!obj) return;
var script = io.slurp(obj);
obj = eval_env(script, this, obj);
}
Object.assign(this, obj);
if (obj)
Object.mixin(this, obj);
},
});
@ -771,6 +785,28 @@ Object.defineProperty(String.prototype, 'set_ext', {
value: function(val) { return this.strip_ext() + val; }
});
Object.defineProperty(String.prototype, 'folder_same_name', {
value: function() {
var dirs = this.dir().split('/');
return dirs.last() === this.name();
}
});
Object.defineProperty(String.prototype, 'up_path', {
value: function() {
var base = this.base();
var dirs = this.dir().split('/');
dirs.pop();
return dirs.join('/') + base;
}
});
Object.defineProperty(String.prototype, 'resolve', {
value: function(path) {
},
});
Object.defineProperty(String.prototype, 'fromlast', {
value: function(val) {
var idx = this.lastIndexOf(val);
@ -816,7 +852,10 @@ Object.defineProperty(String.prototype, 'base', {
});
Object.defineProperty(String.prototype, 'dir', {
value: function() { return this.tolast('/'); }
value: function() {
if (!this.includes('/')) return "";
return this.tolast('/');
}
});
Object.defineProperty(String.prototype, 'splice', {

19
scripts/camera2d.jso Normal file
View file

@ -0,0 +1,19 @@
this.phys = physics.kinematic;
this.dir_view2world = function(dir) { return dir.scale(this.realzoom()); };
this.view2world = function(pos) { return cmd(137,pos); };
this.world2view = function(pos) { return cmd(136,pos); };
this.realzoom = function() { return cmd(135); };
this.mixin({
get zoom() {
var z = Game.native.y / Window.dimensions.y;
return cmd(135)/z;
},
set zoom(x) {
x = Math.clamp(x,0.1,10);
var z = Game.native.y / Window.dimensions.y;
z *= x;
cmd(62,z);
},
});

View file

@ -42,8 +42,7 @@ var component = {
extend(spec) { return Object.copy(this, spec); },
};
component.util = {};
component.util.make_point_obj = function(o, p)
var make_point_obj = function(o, p)
{
return {
pos: p,
@ -56,7 +55,7 @@ component.util.make_point_obj = function(o, p)
}
}
component.util.assign_impl = function(obj, impl)
var assign_impl = function(obj, impl)
{
var tmp = {};
for (var key of Object.keys(impl))
@ -172,7 +171,7 @@ component.sprite.impl = {
var dim = this.dimensions();
dim = dim.scale(this.gameobject.gscale());
var realpos = dim.scale(0.5).add(this.pos);
return cwh2bb(realpos,dim);
return bbox.fromcwh(realpos,dim);
},
kill() { cmd(9,this.id); },
@ -801,7 +800,7 @@ component.circle2d = Object.copy(collider2d, {
toString() { return "circle2d"; },
boundingbox() {
return cwh2bb(this.offset.scale(this.gameobject.scale), [this.radius,this.radius]);
return bbox.fromcwh(this.offset.scale(this.gameobject.scale), [this.radius,this.radius]);
},
hides: ['gameobject', 'id', 'shape', 'scale'],

View file

@ -1,77 +1,3 @@
/* All draw in screen space */
Object.assign(render, {
point(pos,size,color) {
color ??= Color.blue;
render.circle(pos,size,color);
},
line(points, color, thickness) {
thickness ??= 1;
color ??= Color.white;
cmd(83, points, color, thickness);
},
poly(points, color) { cmd_points(0,points,color); },
circle(pos, radius, color) { cmd(115, pos, radius, color); },
/* size here is arm length - size of 2 is 4 height total */
cross(pos, size, color) {
color ??= Color.red;
var a = [
pos.add([0,size]),
pos.add([0,-size])
];
var b = [
pos.add([size,0]),
pos.add([-size,0])
];
render.line(a,color);
render.line(b,color);
},
arrow(start, end, color, wingspan, wingangle) {
color ??= Color.red;
wingspan ??= 4;
wingangle ??=10;
var dir = end.sub(start).normalized();
var wing1 = [
Vector.rotate(dir, wingangle).scale(wingspan).add(end),
end
];
var wing2 = [
Vector.rotate(dir,-wingangle).scale(wingspan).add(end),
end
];
render.line([start,end],color);
render.line(wing1,color);
render.line(wing2,color);
},
rectangle(lowerleft, upperright, color) {
var pos = lowerleft.add(upperright).map(x=>x/2);
var wh = [upperright.x-lowerleft.x,upperright.y-lowerleft.y];
render.box(pos,wh,color);
},
box(pos, wh, color) {
color ??= Color.white;
cmd(53, pos, wh, color);
},
});
render.doc = "Draw shapes in screen space.";
render.circle.doc = "Draw a circle at pos, with a given radius and color.";
render.cross.doc = "Draw a cross centered at pos, with arm length size.";
render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle.";
render.poly.doc = "Draw a concave polygon from a set of points.";
render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright.";
render.box.doc = "Draw a box centered at pos, with width and height in the tuple wh.";
render.line.doc = "Draw a line from a set of points, and a given thickness.";
var Debug = {
fn_break(fn, obj) {
if (typeof fn !== 'function') return;
@ -339,7 +265,7 @@ Time.doc.time = "Seconds elapsed since the game started.";
Time.doc.pause = "Pause the game by setting the timescale to 0; remembers the current timescale on play.";
Time.doc.play = "Resume the game after using Time.pause.";
Player.players[0].control(DebugControls);
player[0].control(DebugControls);
Register.gui.register(Debug.draw, Debug);
Debug.api = {};

View file

@ -32,6 +32,7 @@ function ediff(from,to)
if (typeof v === 'function') return;
if (typeof v === 'undefined') return;
if (Array.isArray(v)) {
if (!Array.isArray(to[key]) || v.length !== to[key].length) {
var r = ediff(v,[]);
@ -54,7 +55,8 @@ function ediff(from,to)
}
if (typeof v === 'number') {
if (!to || v !== to[key])
if (!isFinite(v)) v = null; // Squash infinity to null
if (v !== to[key])
ret[key] = v;
return;
}
@ -67,7 +69,7 @@ function ediff(from,to)
return ret;
}
ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive.";
ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive. If one element in an array is different, the entire array is copied. Squashes infinite numbers to null for use in JSON.";
function samediff(from, to)
{

View file

@ -2,15 +2,12 @@
Editor-only variables on objects
selectable
*/
//prototypes.generate_ur('.');
var editor = {
toString() { return "editor"; },
grid_size: 100,
ruler_mark_px: 100,
grid_color: Color.green.alpha(0.3),
dbg_ur: "arena.level1",
machine: undefined,
device_test: undefined,
selectlist: [],
@ -40,10 +37,10 @@ var editor = {
if (!obj) return;
if (!obj._ed.selectable) return undefined;
if (obj.level !== this.edit_level) {
var testlevel = obj.level;
while (testlevel && testlevel.level !== Primum && testlevel.level !== this.edit_level && testlevel !== testlevel.level)
testlevel = testlevel.level;
if (obj.master !== this.edit_level) {
var testlevel = obj.master;
while (testlevel && testlevel.master !== world && testlevel.master !== this.edit_level && testlevel !== testlevel.master)
testlevel = testlevel.master;
return testlevel;
}
@ -57,7 +54,7 @@ var editor = {
curpanel: undefined,
check_level_nested() {
if (this.edit_level.level) {
if (this.edit_level.master) {
this.openpanel(gen_notify("Can't close a nested level. Save up to the root before continuing."));
return true;
}
@ -186,45 +183,45 @@ var editor = {
start_play_ed() {
this.stash = this.desktop.instance_obj();
Primum.clear();
load("config.js");
world.clear();
global.mixin("config.js");
Game.play();
Player.players[0].uncontrol(this);
Player.players[0].control(limited_editor);
player[0].uncontrol(this);
player[0].control(limited_editor);
editor.cbs.forEach(cb => cb());
editor.cbs = [];
load("predbg.js");
global.mixin("predbg.js");
console.warn(`starting game with ${this.dbg_ur}`);
editor.dbg_play = Primum.spawn(this.dbg_ur);
editor.dbg_play = world.spawn(this.dbg_ur);
editor.dbg_play.pos = [0,0];
load("debug.js");
global.mixin("debug.js");
},
start_play() {
Primum.clear();
load("config.js");
world.clear();
global.mixin("config.js");
Game.play();
Player.players[0].uncontrol(this);
Player.players[0].control(limited_editor);
player[0].uncontrol(this);
player[0].control(limited_editor);
editor.cbs.forEach(cb=>cb());
editor.cbs = [];
load("game.js");
global.mixin("game.js");
},
cbs: [],
enter_editor() {
Game.pause();
Player.players[0].control(this);
Player.players[0].uncontrol(limited_editor);
player[0].control(this);
player[0].uncontrol(limited_editor);
editor.cbs.push(Register.gui.register(editor.gui.bind(editor)));
editor.cbs.push(Register.draw.register(editor.draw.bind(editor)));
editor.cbs.push(Register.debug.register(editor.ed_debug.bind(editor)));
editor.cbs.push(Register.update.register(GUI.controls.update, GUI.controls));
this.desktop = Primum.spawn(ur.arena);
Primum.rename_obj(this.desktop.toString(), "desktop");
this.desktop = world.spawn();
world.rename_obj(this.desktop.toString(), "desktop");
this.edit_level = this.desktop;
editor.edit_level._ed.selectable = false;
if (this.stash) {
@ -232,7 +229,7 @@ var editor = {
Object.dainty_assign(this.desktop, this.stash);
}
this.selectlist = [];
editor.camera = Primum.spawn(ur.camera2d);
editor.camera = world.spawn("scripts/camera2d.jso");
editor.camera._ed.selectable = false;
Game.view_camera(editor.camera);
},
@ -244,11 +241,11 @@ var editor = {
openpanel(panel) {
if (this.curpanel) {
this.curpanel.close();
Player.players[0].uncontrol(this.curpanel);
player[0].uncontrol(this.curpanel);
}
this.curpanel = panel;
Player.players[0].control(this.curpanel);
player[0].control(this.curpanel);
this.curpanel.open();
},
@ -271,6 +268,7 @@ var editor = {
},
snapshot() {
return; // TODO: Implement
var dif = this.edit_level.json_obj();
if (!dif) return;
@ -338,10 +336,6 @@ var editor = {
}
},
load_desktop(d) {
},
draw_objects_names(obj,root,depth){
if (!obj) return;
if (!obj.objects) return;
@ -358,13 +352,13 @@ var editor = {
get sel_comp() { return this._sel_comp; },
set sel_comp(x) {
if (this._sel_comp)
Player.players[0].uncontrol(this._sel_comp);
player[0].uncontrol(this._sel_comp);
this._sel_comp = x;
if (this._sel_comp) {
console.info("sel comp is now " + this._sel_comp);
Player.players[0].control(this._sel_comp);
player[0].control(this._sel_comp);
}
},
@ -413,9 +407,9 @@ var editor = {
var clvl = thiso;
var lvlchain = [];
while (clvl !== Primum) {
while (clvl !== world) {
lvlchain.push(clvl);
clvl = clvl.level;
clvl = clvl.master;
}
lvlchain.push(clvl);
@ -450,11 +444,7 @@ var editor = {
GUI.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]);
this.selectlist.forEach(function(x) {
var sname = x.__proto__.toString();
x.check_dirty();
if (x._ed.dirty) sname += "*";
GUI.text(sname, x.screenpos().add([0, 32]), 1, Color.editor.ur);
GUI.text(x.urstr(), x.screenpos().add([0, 32]), 1, Color.editor.ur);
GUI.text(x.worldpos().map(function(x) { return Math.round(x); }), x.screenpos(), 1, Color.white);
render.cross(x.screenpos(), 10, Color.blue);
});
@ -532,9 +522,7 @@ var editor = {
lvl_history: [],
load(file) {
var ur = prototypes.get_ur(file);
if (!ur) return;
var obj = editor.edit_level.spawn(ur);
var obj = editor.edit_level.spawn(Object.access(ur, file));
obj.set_worldpos(Mouse.worldpos);
this.selectlist = [obj];
},
@ -550,10 +538,11 @@ var editor = {
/* Checking to save an entity as a subtype. */
/* sub is the name of the (sub)type; obj is the object to save it as */
saveas_check(sub, obj) {
return;
if (!sub) return;
obj ??= editor.selectlist[0];
var curur = prototypes.get_ur(sub);
// var curur = prototypes.get_ur(sub);
if (curur) {
notifypanel.action = editor.saveas;
@ -570,7 +559,7 @@ var editor = {
editor.selectlist = [nobj];
return;
}
editor.edit_level = editor.edit_level.level;
editor.edit_level = editor.edit_level.master;
}
var t = obj.transform();
@ -633,7 +622,7 @@ editor.inputs.n = function() {
var o = editor.try_select();
if (!o) return;
if (o === editor.selectlist[0]) return;
if (o.level !== editor.selectlist[0].level) return;
if (o.master !== editor.selectlist[0].master) return;
var tpos = editor.selectlist[0].pos;
tpos.x *= -1;
@ -646,7 +635,7 @@ editor.inputs['M-n'] = function()
var o = editor.try_select();
if (!o) return;
if (o === editor.selectlist[0]) return;
if (o.level !== editor.selectlist[0].level) return;
if (o.master !== editor.selectlist[0].master) return;
var tpos = editor.selectlist[0].pos;
tpos.y *= -1;
@ -662,7 +651,7 @@ editor.inputs['h'] = function() {
};
editor.inputs['h'].doc = "Toggle object hidden.";
editor.inputs['C-h'] = function() { Primum.objects.forEach(function(x) { x.visible = true; }); };
editor.inputs['C-h'] = function() { world.objects.forEach(function(x) { x.visible = true; }); };
editor.inputs['C-h'].doc = "Unhide all objects.";
editor.inputs['C-e'] = function() { editor.openpanel(assetexplorer); };
@ -733,9 +722,9 @@ editor.inputs['C-f'] = function() {
editor.inputs['C-f'].doc = "Tunnel into the selected level object to edit it.";
editor.inputs['C-F'] = function() {
if (editor.edit_level.level === Primum) return;
if (editor.edit_level.master === world) return;
editor.edit_level = editor.edit_level.level;
editor.edit_level = editor.edit_level.master;
editor.unselect();
editor.reset_undos();
};
@ -807,7 +796,7 @@ editor.inputs['C-space'] = function() {
editor.inputs['C-space'].doc = "Search to execute a specific command.";
editor.inputs['M-m'] = function() {
// Player.players[0].control(rebinder);
// player[0].control(rebinder);
};
editor.inputs['M-m'].doc = "Rebind a shortcut. Usage: M-m SHORTCUT TARGET";
@ -838,7 +827,8 @@ editor.inputs['C-s'] = function() {
var savejs = saveobj.json_obj();
Object.merge(saveobj.__proto__, savejs);
if (savejs.objects) saveobj.__proto__.objects = savejs.objects;
var path = prototypes.ur_stem(saveobj.ur.toString()) + ".json";
// var path = prototypes.ur_stem(saveobj.ur.toString()) + ".json";
path = "CHANGETHIS";
io.slurpwrite(path, JSON.stringify(saveobj.__proto__,null,1));
console.warn(`Wrote to file ${path}`);
@ -1053,7 +1043,7 @@ editor.inputs['C-M-lm'] = function()
{
var go = physics.pos_query(Mouse.worldpos);
if (!go) return;
editor.edit_level = go.level;
editor.edit_level = go.master;
}
editor.inputs['C-M-mm'] = function() {
@ -1380,7 +1370,7 @@ var inputpanel = {
start() {},
close() {
Player.players[0].uncontrol(this);
player[0].uncontrol(this);
this.on = false;
if ('on_close' in this)
this.on_close();
@ -1505,6 +1495,7 @@ var replpanel = Object.copy(inputpanel, {
this.value = "";
this.caret = 0;
var ret = function() {return eval(ecode);}.call(repl_obj);
if (typeof ret === 'object') ret = json.encode(ret,null,1);
console.say(ret);
},
@ -1797,7 +1788,7 @@ var openlevelpanel = Object.copy(inputpanel, {
},
start() {
this.allassets = prototypes.list.sort();
this.allassets = ur._list.sort();
this.assets = this.allassets.slice();
this.caret = 0;
var click_ur = function(btn) {
@ -1954,7 +1945,7 @@ var entitylistpanel = Object.copy(inputpanel, {
title: "Level object list",
level: {},
start() {
this.level = editor.edit_level;
this.master = editor.edit_level;
},
});
@ -1978,9 +1969,9 @@ limited_editor.inputs['M-p'] = function()
limited_editor.inputs['C-q'] = function()
{
Primum.clear();
load("editorconfig.js");
load("dbgret.js");
world.clear();
global.mixin("editorconfig.js");
global.mixin("dbgret.js");
editor.enter_editor();
}

View file

@ -14,19 +14,28 @@ function use(file)
}
use.files = {};
function include(file,that)
{
if (!that) return;
var c = io.slurp(file);
eval_env(c, that, file);
}
function eval_env(script, env, file)
{
env ??= {};
file ??= "SCRIPT";
// script = `(function() { ${script} })();`;
eval(`(function() { ${script} }).call(env);`);
// cmd(123,script,global,file);
// return eval(script);
// return function(str) { return eval(str); }.call(env, script);
script = `(function() { ${script} }).call(this);`;
return cmd(123,script,env,file);
}
eval_env.dov = `Counterpart to /load_env/, but with a string.`;
function feval_env(file, env)
{
eval_env(io.slurp(file), env, file);
}
function load_env(file,env)
{
env ??= global;
@ -43,12 +52,12 @@ var load = use;
Object.assign(global, use("scripts/base.js"));
global.obscure('global');
global.mixin(use("scripts/std.js"));
global.mixin(use("scripts/diff.js"));
global.mixin("scripts/std.js");
global.mixin("scripts/diff.js");
console.level = 1;
global.mixin(use("scripts/color.js"));
global.mixin("scripts/color.js");
var prosperon = {};
prosperon.version = cmd(255);
@ -104,7 +113,7 @@ Range is given by a semantic versioning number, prefixed with nothing, a ~, or a
^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`;
global.mixin(use("scripts/gui.js"));
global.mixin("scripts/gui.js");
var timer = {
update(dt) {
@ -130,61 +139,13 @@ var timer = {
},
};
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."
};
render.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],
};
render.device.doc = `Device resolutions given as [x,y,inches diagonal].`;
global.mixin(use("scripts/physics.js"));
global.mixin(use("scripts/input.js"));
global.mixin(use("scripts/sound.js"));
global.mixin(use("scripts/ai.js"));
global.mixin(use("scripts/geometry.js"));
global.mixin("scripts/tween.js");
global.mixin("scripts/render.js");
global.mixin("scripts/physics.js");
global.mixin("scripts/input.js");
global.mixin("scripts/sound.js");
global.mixin("scripts/ai.js");
global.mixin("scripts/geometry.js");
var Register = {
kbm_input(mode, btn, state, ...args) {
@ -194,15 +155,15 @@ var Register = {
switch(mode) {
case "emacs":
Player.players[0].raw_input(btn, state, ...args);
player[0].raw_input(btn, state, ...args);
break;
case "mouse":
Player.players[0].mouse_input(btn, state, ...args);
player[0].mouse_input(btn, state, ...args);
break;
case "char":
Player.players[0].char_input(btn);
player[0].char_input(btn);
break;
};
},
@ -275,7 +236,7 @@ Register.add_cb(10, "draw");
register(9, console.stack, this);
Register.gamepad_playermap[0] = Player.players[0];
Register.gamepad_playermap[0] = player[0];
var Event = {
events: {},
@ -320,21 +281,21 @@ var Window = {
};
Window.screen2world = function(screenpos) {
// if (Game.camera)
// return Game.camera.view2world(screenpos);
if (Game.camera)
return Game.camera.view2world(screenpos);
return screenpos;
}
Window.world2screen = function(worldpos) {
return worldpos;
return Game.camera.world2view(worldpos);
}
Window.icon = function(path) { cmd(90, path); };
Window.icon.doc = "Set the icon of the window using the PNG image at path.";
global.mixin(use("scripts/debug.js"));
global.mixin(use("scripts/spline.js"));
global.mixin(use("scripts/components.js"));
global.mixin("scripts/debug.js");
global.mixin("scripts/spline.js");
global.mixin("scripts/components.js");
var Game = {
engine_start(fn) {
@ -420,31 +381,33 @@ Window.doc.boundingbox = "Boundingbox of the window, with top and right being it
Register.update.register(Game.exec, Game);
global.mixin(use("scripts/entity.js"));
global.mixin("scripts/actor.js");
global.mixin("scripts/entity.js");
function world_start() {
globalThis.Primum = Object.create(gameobject);
Primum.objects = {};
Primum.check_dirty = function() {};
Primum.namestr = function(){};
Primum._ed = {
globalThis.world = Object.create(gameobject);
world.objects = {};
world.check_dirty = function() {};
world.namestr = function(){};
world._ed = {
selectable:false,
dirty:false,
};
Primum.toString = function() { return "Primum"; };
Primum.ur = "Primum";
Primum.kill = function() { this.clear(); };
Primum.phys = 2;
world.toString = function() { return "world"; };
world.master = gameobject;
world.ur = "world";
world.kill = function() { this.clear(); };
world.phys = 2;
gameobject.level = Primum;
gameobject.level = world;
gameobject.body = make_gameobject();
cmd(113,gameobject.body, gameobject);
Object.hide(gameobject, 'timescale');
global.world = Primum;
var cam = world.spawn("scripts/camera2d.jso");
Game.view_camera(cam);
}
global.mixin(use("scripts/physics.js"));
global.mixin("scripts/physics.js");
Game.view_camera = function(cam)
{
@ -452,8 +415,6 @@ Game.view_camera = function(cam)
cmd(61, Game.camera.body);
}
prototypes.generate_ur('scripts/camera.jso');
Window.title(`Prosperon v${prosperon.version}`);
Window.width = 1280;
Window.height = 720;

View file

@ -1,4 +1,4 @@
prosperon.obj_unique_name = function(name, obj)
function obj_unique_name(name, obj)
{
name = name.replaceAll('.', '_');
if (!(name in obj)) return name;
@ -11,92 +11,55 @@ prosperon.obj_unique_name = function(name, obj)
return n;
}
var actor = {};
var a_db = {};
actor.spawn = function(script, config){
if (typeof script !== 'string') return undefined;
if (!a_db[script]) a_db[script] = io.slurp(script);
var padawan = Object.create(actor);
eval_env(a_db[script], padawan);
if (typeof config === 'object')
Object.merge(padawan,config);
padawan.padawans = [];
padawan.timers = [];
padawan.master = this;
Object.hide(padawan, "master","timers", "padawans");
this.padawans.push(padawan);
return padawan;
};
actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`;
actor.timers = [];
actor.kill = function(){
if (this.__dead__) return;
this.timers.forEach(t => t.kill());
if (this.master)
delete this.master[this.toString()];
this.padawans.forEach(p => p.kill());
this.padawans = [];
this.__dead__ = true;
if (typeof this.die === 'function') this.die();
};
actor.kill.doc = `Remove this actor and all its padawans from existence.`;
actor.delay = function(fn, seconds) {
var t = Object.create(timer);
t.remain = seconds;
t.kill = () => {
timer.kill.call(t);
delete this.timers[t.toString()];
}
t.fire = () => {
if (this.__dead__) return;
fn();
t.kill();
};
Register.appupdate.register(t.update, t);
this.timers.push(t);
return function() { t.kill(); };
};
actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`;
actor.master = undefined;
actor.padawans = [];
actor.remaster = function(to){
delete this.master.padawans[this.toString()];
this.master = to;
to.padawans.push(this);
};
global.app = Object.create(actor);
app.die = function()
function check_registers(obj)
{
Game.quit();
if (typeof obj.update === 'function')
obj.timers.push(Register.update.register(obj.update.bind(obj)));
if (typeof obj.physupdate === 'function')
obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj)));
if (typeof obj.collide === 'function')
register_collide(0, obj.collide.bind(obj), obj.body);
if (typeof obj.separate === 'function')
register_collide(3,obj.separate.bind(obj), obj.body);
if (typeof obj.draw === 'function')
obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj));
if (typeof obj.debug === 'function')
obj.timers.push(Register.debug.register(obj.debug.bind(obj)));
if (typeof obj.gui === 'function')
obj.timers.push(Register.gui.register(obj.gui.bind(obj)));
for (var k in obj) {
if (!k.startswith("on_")) continue;
var signal = k.fromfirst("on_");
Event.observe(signal, obj, obj[k]);
};
obj.components.forEach(function(x) {
if (typeof x.collide === 'function')
register_collide(1, x.collide.bind(x), obj.body, x.shape);
});
}
var gameobject_impl = {
get pos() {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
return this.level.world2this(this.worldpos());
Debug.assert(this.master, `Entity ${this.toString()} has no master.`);
return this.master.world2this(this.worldpos());
},
set pos(x) {
Debug.assert(this.level, `Entity ${this.toString()} has no level.`);
this.set_worldpos(this.level.this2world(x));
Debug.assert(this.master, `Entity ${this.toString()} has no master.`);
this.set_worldpos(this.master.this2world(x));
},
get angle() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
return this.worldangle() - this.level.worldangle();
Debug.assert(this.master, `No master set on ${this.toString()}`);
return this.worldangle() - this.master.worldangle();
},
set angle(x) {
@ -107,18 +70,18 @@ var gameobject_impl = {
x.pos = Vector.rotate(x.pos, diff);
});
this.sworldangle(x-this.level.worldangle());
this.sworldangle(x-this.master.worldangle());
},
get scale() {
Debug.assert(this.level, `No level set on ${this.toString()}`);
var pscale;
if (typeof this.__proto__.scale === 'object')
pscale = this.__proto__.scale;
Debug.assert(this.master, `No master set on ${this.toString()}`);
var pscale = [1,1,1];
/* if (typeof this.master.scale === 'object')
pscale = this.master.scale;
else
pscale = [1,1,1];
return this.gscale().map((x,i) => x/(this.level.gscale()[i]*pscale[i]));
*/
return this.gscale().map((x,i) => x/(this.master.gscale()[i]*pscale[i]));
},
set scale(x) {
@ -187,9 +150,11 @@ var gameobject = {
return undefined;
},
check_dirty() {
// TODO: IMPLEMENT
return;
this._ed.urdiff = this.json_obj();
this._ed.dirty = !Object.empty(this._ed.urdiff);
var lur = ur[this.level.ur];
var lur = ur[this.master.ur];
if (!lur) return;
var lur = lur.objects[this.toString()];
var d = ediff(this._ed.urdiff,lur);
@ -202,6 +167,7 @@ var gameobject = {
selectable: false,
dirty: false
},
namestr() {
var s = this.toString();
if (this._ed.dirty)
@ -209,8 +175,14 @@ var gameobject = {
else s += "*";
return s;
},
urstr() {
if (this._ed.dirty) return "*"+this.ur;
return this.ur;
},
full_path() {
return this.path_from(Primum);
return this.path_from(world);
},
/* pin this object to the to object */
pin(to) {
@ -270,12 +242,12 @@ var gameobject = {
path_from(o) {
var p = this.toString();
var c = this.level;
while (c && c !== o && c !== Primum) {
var c = this.master;
while (c && c !== o && c !== world) {
p = c.toString() + "." + p;
c = c.level;
c = c.master;
}
if (c === Primum) p = "Primum." + p;
if (c === world) p = "world." + p;
return p;
},
@ -334,37 +306,110 @@ var gameobject = {
worldangle() { return Math.rad2turn(q_body(2,this.body)); },
sworldangle(x) { set_body(0,this.body,Math.turn2rad(x)); },
spawn_from_instance(inst) {
return this.spawn(inst.ur, inst);
},
spawn(ur, data) {
ur ??= gameobject;
if (typeof ur === 'string') {
//ur = prototypes.get_ur(ur);
}
/* spawn an entity
text can be:
the file path of a script
an ur object
nothing
*/
spawn(text) {
var ent = Object.create(gameobject);
if (typeof text === 'object')
text = text.name;
var go = ur.make(this, data);
Object.hide(this, go.toString());
return go;
if (typeof text === 'undefined')
ent.ur = "new";
else if (typeof text !== 'string') {
console.error(`Must pass in an ur type or a string to make an entity.`);
return;
} else {
if (Object.access(ur,text))
ent.ur = text;
else if (io.exists(text))
ent.ur = "script";
else {
console.warn(`Cannot make an entity from '${text}'. Not a valid ur.`);
return;
}
}
Object.mixin(ent,gameobject_impl);
ent.body = make_gameobject();
ent.components = {};
ent.objects = {};
ent.timers = [];
ent.reparent(this);
ent._ed = {
selectable: true,
dirty: false,
inst: false,
urdiff: {},
};
cmd(113, ent.body, ent); // set the internal obj reference to this obj
Object.hide(ent, 'ur','body', 'components', 'objects', '_ed', 'timers', 'master');
if (ent.ur === 'script')
eval_env(io.slurp(text), ent, ent.ur);
else if (ent.ur !== 'new')
apply_ur(ent.ur, ent);
for (var [prop,p] of Object.entries(ent)) {
if (!p) continue;
if (typeof p !== 'object') continue;
if (component.isComponent(p)) continue;
if (!p.comp) continue;
ent[prop] = component[p.comp].make(ent);
Object.merge(ent[prop], p);
ent.components[prop] = ent[prop];
};
check_registers(ent);
if (typeof ent.load === 'function') ent.load();
if (typeof ent.start === 'function') ent.start();
var mur = Object.access(ur,ent.ur);
if (mur && !mur.proto)
mur.proto = json.decode(json.encode(ent));
if (!Object.empty(ent.objects)) {
var o = ent.objects;
delete ent.objects;
for (var i in o) {
say(`MAKING ${i}`);
var n = ent.spawn(ur[o[i].ur]);
ent.rename_obj(n.toString(), i);
delete o[i].ur;
Object.assign(n, o[i]);
}
}
return ent;
},
/* Reparent 'this' to be 'parent's child */
reparent(parent) {
Debug.assert(parent, `Tried to reparent ${this.toString()} to nothing.`);
if (this.level === parent) {
if (this.master === parent) {
console.warn("not reparenting ...");
console.warn(`${this.level} is the same as ${parent}`);
console.warn(`${this.master} is the same as ${parent}`);
return;
}
this.level?.remove_obj(this);
this.master?.remove_obj(this);
this.level = parent;
this.master = parent;
function unique_name(list, obj) {
var str = obj.toString().replaceAll('.', '_');
function unique_name(list, name) {
name ??= "new_object";
var str = name.replaceAll('.', '_');
var n = 1;
var t = str;
while (t in list) {
@ -374,7 +419,7 @@ var gameobject = {
return t;
};
var name = unique_name(parent, this.ur);
var name = unique_name(Object.keys(parent.objects), this.ur);
parent.objects[name] = this;
parent[name] = this;
@ -391,7 +436,7 @@ var gameobject = {
components: {},
objects: {},
level: undefined,
master: undefined,
pulse(vec) { set_body(4, this.body, vec);},
shove(vec) { set_body(12,this.body,vec);},
@ -427,7 +472,7 @@ var gameobject = {
/* Make a unique object the same as its prototype */
revert() {
var jobj = this.json_obj();
var lobj = this.level.__proto__.objects[this.toString()];
var lobj = this.master.__proto__.objects[this.toString()];
delete jobj.objects;
Object.keys(jobj).forEach(function(x) {
if (lobj && x in lobj)
@ -438,53 +483,15 @@ var gameobject = {
this.sync();
},
unregister() {
this.timers.forEach(t=>t());
this.timers = [];
},
check_registers(obj) {
obj.unregister();
if (typeof obj.update === 'function')
obj.timers.push(Register.update.register(obj.update.bind(obj)));
if (typeof obj.physupdate === 'function')
obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj)));
if (typeof obj.collide === 'function')
register_collide(0, obj.collide.bind(obj), obj.body);
if (typeof obj.separate === 'function')
register_collide(3,obj.separate.bind(obj), obj.body);
if (typeof obj.draw === 'function')
obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj));
if (typeof obj.debug === 'function')
obj.timers.push(Register.debug.register(obj.debug.bind(obj)));
if (typeof obj.gui === 'function')
obj.timers.push(Register.gui.register(obj.gui.bind(obj)));
for (var k in obj) {
if (!k.startswith("on_")) continue;
var signal = k.fromfirst("on_");
Event.observe(signal, obj, obj[k]);
};
obj.components.forEach(function(x) {
if (typeof x.collide === 'function')
register_collide(1, x.collide.bind(x), obj.body, x.shape);
});
},
toString() { return "new_object"; },
flipx() { return this.scale.x < 0; },
flipy() { return this.scale.y < 0; },
mirror(plane) {
this.scale = Vector.reflect(this.scale, plane);
},
flipx() { return this.scale.x < 0; },
flipy() { return this.scale.y < 0; },
mirror(plane) {
this.scale = Vector.reflect(this.scale, plane);
},
save:true,
selectable:true,
ed_locked:false,
@ -524,17 +531,22 @@ var gameobject = {
/* The unique components of this object. Its diff. */
json_obj() {
var d = ediff(this,this.__proto__);
var u = Object.access(ur,this.ur);
if (!u) return {};
var proto = u.proto;
var thiso = json.decode(json.encode(this)); // TODO: SLOW. Used to ignore properties in toJSON of components.
var d = ediff(thiso,proto);
d ??= {};
var objects = {};
this.__proto__.objects ??= {};
proto.objects ??= {};
var curobjs = {};
for (var o in this.objects)
curobjs[o] = this.objects[o].instance_obj();
var odiff = ediff(curobjs, this.__proto__.objects);
var odiff = ediff(curobjs, proto.objects);
if (odiff)
d.objects = curobjs;
@ -546,15 +558,19 @@ var gameobject = {
return d;
},
/* The object needed to store an object as an instance of a level */
/* The object needed to store an object as an instance of a master */
instance_obj() {
var t = this.transform();
// var j = this.json_obj();
// Object.assign(t,j);
t.ur = this.ur;
return t;
},
proto() {
var u = Object.access(ur,this.ur);
if (!u) return {};
return u.proto;
},
transform() {
var t = {};
t.pos = this.pos;
@ -562,7 +578,7 @@ var gameobject = {
t.angle = Math.places(this.angle,4);
if (t.angle === 0) delete t.angle;
t.scale = this.scale;
t.scale = t.scale.map((x,i) => x/this.__proto__.scale[i]);
t.scale = t.scale.map((x,i) => x/this.proto().scale[i]);
t.scale = t.scale.map(x => Math.places(x,3));
if (t.scale.every(x=>x===1)) delete t.scale;
return t;
@ -577,7 +593,7 @@ var gameobject = {
},
dup(diff) {
var n = this.level.spawn(this.__proto__);
var n = this.master.spawn(this.__proto__);
Object.totalmerge(n, this.instance_obj());
return n;
},
@ -592,9 +608,9 @@ var gameobject = {
Player.do_uncontrol(this);
register_collide(2, undefined, this.body);
if (this.level) {
this.level.remove_obj(this);
this.level = undefined;
if (this.master) {
this.master.remove_obj(this);
this.master = undefined;
}
if (this.__proto__.instances)
@ -608,10 +624,9 @@ var gameobject = {
this.clear();
this.objects = undefined;
if (typeof this.stop === 'function')
this.stop();
if (typeof this.stop === 'function') this.stop();
if (typeof this.die === 'function') this.die();
},
up() { return [0,1].rotate(this.angle);},
@ -619,63 +634,10 @@ var gameobject = {
right() { return [1,0].rotate(this.angle);},
left() { return [-1,0].rotate(this.angle); },
make() {
var obj = Object.create(this);
obj.make = undefined;
Object.mixin(obj,gameobject_impl);
obj.body = make_gameobject();
obj.components = {};
obj.objects = {};
obj.timers = [];
obj._ed = {
selectable: true,
dirty: false,
inst: false,
urdiff: {},
};
obj.ur = this.toString();
obj.level = undefined;
obj.reparent(level);
cmd(113, obj.body, obj); // set the internal obj reference to this obj
for (var [prop,p] of Object.entries(this)) {
if (!p) continue;
if (component.isComponent(p)) {
obj[prop] = p.make(obj);
obj.components[prop] = obj[prop];
}
};
Object.hide(obj, 'ur','body', 'components', 'objects', '_ed', 'level', 'timers');
if (this.objects)
obj.make_objs(this.objects)
Object.dainty_assign(obj, this);
obj.sync();
gameobject.check_registers(obj);
if (data)
Object.dainty_assign(obj,data);
if (typeof obj.load === 'function') obj.load();
if (Game.playing() && typeof obj.start === 'function') obj.start();
return obj;
},
make_objs(objs) {
for (var prop in objs) {
var newobj = this.spawn_from_instance(objs[prop]);
if (!newobj) continue;
this.rename_obj(newobj.toString(), prop);
say(`spawning ${json.encode(objs[prop])}`);
var newobj = this.spawn(objs[prop]);
}
},
@ -700,10 +662,11 @@ var gameobject = {
return this.objects[newname];
},
add_component(comp, data) {
add_component(comp, data, name) {
data ??= undefined;
if (typeof comp.make !== 'function') return;
var name = prosperon.obj_unique_name(comp.toString(), this);
name ??= comp.toString();
name = obj_unique_name(name, this);
this[name] = comp.make(this);
this[name].comp = comp.toString();
this.components[name] = this[name];
@ -724,11 +687,11 @@ gameobject.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the
gameobject.doc = {
doc: "All objects in the game created through spawning have these attributes.",
pos: "Position of the object, relative to its level.",
angle: "Rotation of this object, relative to its level.",
pos: "Position of the object, relative to its master.",
angle: "Rotation of this object, relative to its master.",
velocity: "Velocity of the object, relative to world.",
angularvelocity: "Angular velocity of the object, relative to the world.",
scale: "Scale of the object, relative to its level.",
scale: "Scale of the object, relative to its master.",
flipx: "Check if the object is flipped on its x axis.",
flipy: "Check if the object is flipped on its y axis.",
elasticity: `When two objects collide, their elasticities are multiplied together. Their velocities are then multiplied by this value to find their resultant velocities.`,
@ -757,7 +720,7 @@ gameobject.doc = {
dup: `Make an exact copy of this object.`,
transform: `Return an object representing the transform state of this object.`,
kill: `Remove this object from the world.`,
level: "The entity this entity belongs to.",
master: "The entity this entity belongs to.",
delay: 'Run the given function after the given number of seconds has elapsed.',
cry: 'Make a sound. Can only make one at a time.',
add_component: 'Add a component to the object by name.',
@ -773,167 +736,7 @@ gameobject.doc = {
motor: 'Keeps the relative angular velocity of this body to to at a constant rate. The most simple idea is for one of the bodies to be static, to the other is kept at rate.'
};
/* Default objects */
var prototypes = {};
prototypes.ur_ext = ".jso";
prototypes.ur = {};
/* Makes a new ur-type from disk. If the ur doesn't exist, it searches on the disk to create it. */
prototypes.from_file = function(file)
{
var urpath = file;
var path = urpath.split('.');
if (path.length > 1 && (path.at(-1) === path.at(-2))) {
urpath = path.slice(0,-1).join('.');
return prototypes.get_ur(urpath);
}
var upperur = gameobject;
if (path.length > 1) {
var upur = undefined;
var upperpath = path.slice(0,-1);
while (!upur && upperpath) {
upur = prototypes.get_ur(upperpath.join('/'));
upperpath = upperpath.slice(0,-1);
}
if (upur) upperur = upur;
}
var newur = {};
file = file.replaceAll('.','/');
var jsfile = prototypes.get_ur_file(urpath, prototypes.ur_ext);
var jsonfile = prototypes.get_ur_file(urpath, ".json");
var script = undefined;
var json = undefined;
if (jsfile) script = io.slurp(jsfile);
try {
if (jsonfile) json = JSON.parse(io.slurp(jsonfile));
} catch(e) {
console.warn(`Unable to create json from ${jsonfile}. ${e}`);
}
if (!json && !jsfile) {
console.warn(`Could not make ur from ${file}`);
return undefined;
}
if (script)
load_env(jsfile, newur);
json ??= {};
Object.merge(newur,json);
Object.entries(newur).forEach(function([k,v]) {
if (Object.isObject(v) && Object.isObject(upperur[k]))
v.__proto__ = upperur[k];
});
Object.values(newur).forEach(function(v) {
if (typeof v !== 'object') return;
if (!v.comp) return;
v.__proto__ = component[v.comp];
});
newur.__proto__ = upperur;
newur.instances = [];
Object.hide(newur, 'instances');
prototypes.list.push(urpath);
newur.toString = function() { return urpath; };
ur[urpath] = newur;
return newur;
}
prototypes.from_file.doc = "Create a new ur-type from a given script file.";
prototypes.list = [];
prototypes.list_ur = function()
{
var list = [];
function list_obj(obj, prefix)
{
prefix ??= "";
var list = [];
for (var e in obj) {
list.push(prefix + e);
list.concat(list_obj(obj[e], e + "."));
}
return list;
}
return list_obj(ur);
}
prototypes.ur2file = function(urpath)
{
return urpath.replaceAll('.', '/');
}
prototypes.file2ur = function(file)
{
file = file.strip_ext();
file = file.replaceAll('/','.');
return file;
}
prototypes.get_ur = function(name)
{
if (!name) return;
if (!name) {
console.error(`Can't get ur from ${name}.`);
return;
}
var urpath = name;
if (urpath.includes('/'))
urpath = prototypes.file2ur(name);
if (!prototypes.ur[urpath]) {
var ur = prototypes.from_file(urpath);
if (ur)
return ur;
else {
console.warn(`Could not find prototype using name ${name}.`);
return undefined;
}
} else
return prototypes.ur[urpath];
}
prototypes.get_ur.doc = `Returns an ur, or makes it, for any given type of path
could be a file on a disk like ball/big.js
could be an ur path like ball.big`;
prototypes.get_ur_file = function(path, ext)
{
var urpath = prototypes.ur2file(path);
var file = urpath + ext;
if (io.exists(file)) return file;
file = urpath + "/" + path.split('.').at(-1) + ext;
if (io.exists(file)) return file;
return undefined;
}
prototypes.generate_ur = function(path)
{
var ob = io.glob("**" + prototypes.ur_ext);
ob = ob.concat(io.glob("**.json"));
ob = ob.map(function(path) { return path.set_ext(""); });
ob = ob.map(function(path) { return path[0] !== '.' ? path : undefined; });
ob = ob.map(function(path) { return path[0] !== '_' ? path : undefined; });
ob = ob.filter(x => x !== undefined);
ob.forEach(function(name) { prototypes.get_ur(name); });
}
var ur = prototypes.ur;
prototypes.resavi = function(ur, path)
var resavi = function(ur, path)
{
if (!ur) return path;
if (path[0] === '/') return path;
@ -946,7 +749,7 @@ prototypes.resavi = function(ur, path)
return path;
}
prototypes.resani = function(ur, path)
var resani = function(ur, path)
{
if (!path) return "";
if (!ur) return path;
@ -964,57 +767,88 @@ prototypes.resani = function(ur, path)
return restry;
}
prototypes.ur_dir = function(ur)
{
var path = ur.replaceAll('.', '/');
console.warn(path);
console.warn(io.exists(path));
console.warn(`${path} does not exist; sending ${path.dir()}`);
var ur = {};
ur._list = [];
/* UR OBJECT
ur {
name: fully qualified name of ur
text: file path to the script
data: file path to data
proto: resultant object of a freshly made entity
}
*/
prototypes.ur_json = function(ur)
/* Apply an ur u to an entity e */
/* u is given as */
function apply_ur(u, e)
{
var path = ur.replaceAll('.', '/');
if (io.exists(path))
path = path + "/" + path.name() + ".json";
else
path = path + ".json";
return path;
}
prototypes.ur_stem = function(ur)
{
var path = ur.replaceAll('.', '/');
if (io.exists(path))
return path + "/" + path.name();
else
return path;
}
prototypes.ur_file_exts = ['.jso', '.json'];
prototypes.ur_folder = function(ur)
{
var path = ur.replaceAll('.', '/');
return io.exists(path);
}
prototypes.ur_pullout_folder = function(ur)
{
if (!prototypes.ur_folder(ur)) return;
var stem = prototypes.ur_stem(ur);
if (typeof u !== 'string') {
console.warn("Must give u as a string.");
return;
}
/* prototypes.ur_file_exts.forEach(function(e) {
var p = stem + e;
if (io.exists(p))
*/
var urs = u.split('.');
var config = {};
var topur = ur;
for (var i = 0; i < urs.length; i++) {
topur = topur[urs[i]];
if (!topur) {
console.warn(`Ur given by ${u} does not exist. Stopped at ${urs[i]}.`);
return;
}
if (topur.text)
feval_env(topur.text, e);
if (topur.data)
Object.merge(config, json.decode(io.slurp(topur.data)));
}
Object.merge(e, config);
}
function file2fqn(file)
{
var fqn = file.strip_ext();
if (fqn.folder_same_name())
fqn = fqn.up_path();
fqn = fqn.replace('/','.');
var topur;
if (topur = Object.access(ur,fqn)) return topur;
var fqnlast = fqn.split('.').last();
if (topur = Object.access(ur,fqn.tolast('.'))) {
topur[fqnlast] = {
name: fqn
};
ur._list.push(fqn);
return Object.access(ur,fqn);
}
fqn = fqnlast;
ur[fqn] = {
name: fqn
};
ur._list.push(fqn);
return ur[fqn];
}
/* FIND ALL URS IN A PROJECT */
for (var file of io.glob("**.jso")) {
var topur = file2fqn(file);
topur.text = file;
}
for (var file of io.glob("**.json")) {
var topur = file2fqn(file);
topur.data = file;
}
return {
gameobject,
actor,
prototypes,
ur
}

View file

@ -203,6 +203,7 @@ var Player = {
n.pawns = [];
n.gamepads = [];
this.players.push(n);
this[this.players.length-1] = n;
return n;
},
@ -227,9 +228,12 @@ Player.print_pawns.doc = "Print out a list of the current pawn control stack.";
Player.doc = {};
Player.doc.players = "A list of current players.";
var player = Player;
return {
Mouse,
Keys,
input,
Player,
player
};

119
scripts/render.js Normal file
View file

@ -0,0 +1,119 @@
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."
};
render.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],
};
render.device.doc = `Device resolutions given as [x,y,inches diagonal].`;
/* All draw in screen space */
render.point = function(pos,size,color) {
color ??= Color.blue;
render.circle(pos,size,color);
};
render.line = function(points, color, thickness) {
thickness ??= 1;
color ??= Color.white;
cmd(83, points, color, thickness);
};
render.poly = function(points, color) { cmd_points(0,points,color); };
render.circle = function(pos, radius, color) { cmd(115, pos, radius, color); };
render.cross = function(pos, size, color) {
color ??= Color.red;
var a = [
pos.add([0,size]),
pos.add([0,-size])
];
var b = [
pos.add([size,0]),
pos.add([-size,0])
];
render.line(a,color);
render.line(b,color);
};
render.arrow = function(start, end, color, wingspan, wingangle) {
color ??= Color.red;
wingspan ??= 4;
wingangle ??=10;
var dir = end.sub(start).normalized();
var wing1 = [
Vector.rotate(dir, wingangle).scale(wingspan).add(end),
end
];
var wing2 = [
Vector.rotate(dir,-wingangle).scale(wingspan).add(end),
end
];
render.line([start,end],color);
render.line(wing1,color);
render.line(wing2,color);
};
render.rectangle = function(lowerleft, upperright, color) {
var pos = lowerleft.add(upperright).map(x=>x/2);
var wh = [upperright.x-lowerleft.x,upperright.y-lowerleft.y];
render.box(pos,wh,color);
};
render.box = function(pos, wh, color) {
color ??= Color.white;
cmd(53, pos, wh, color);
};
render.doc = "Draw shapes in screen space.";
render.circle.doc = "Draw a circle at pos, with a given radius and color.";
render.cross.doc = "Draw a cross centered at pos, with arm length size.";
render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle.";
render.poly.doc = "Draw a concave polygon from a set of points.";
render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright.";
render.box.doc = "Draw a box centered at pos, with width and height in the tuple wh.";
render.line.doc = "Draw a line from a set of points, and a given thickness.";
return {render};

View file

@ -113,3 +113,5 @@ Object.mixin(cmd(180).__proto__, {
pct() { return this.time()/this.length(); },
});
*/
return {audio};

112
scripts/spline.js Normal file
View file

@ -0,0 +1,112 @@
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); }
return {Spline};

View file

@ -238,6 +238,10 @@ Cmdline.register_order("init", function() {
}, "Turn the directory into a Prosperon game.");
Cmdline.register_order("debug", function() {
Cmdline.orders.play();
}, "Play the game with debugging enabled.");
Cmdline.register_order("play", function() {
if (!io.exists(".prosperon/project")) {
say("No game to play. Try making one with 'prosperon init'.");
@ -247,8 +251,8 @@ Cmdline.register_order("play", function() {
var project = json.decode(io.slurp(".prosperon/project"));
Game.engine_start(function() {
load("config.js");
load("game.js");
global.mixin("config.js");
global.mixin("game.js");
if (project.icon) Window.icon(project.icon);
if (project.title) Window.title(project.title);
});
@ -379,35 +383,6 @@ function cmd_args(cmdargs)
Cmdline.register_order("clean", function(argv) {
say("Cleaning not implemented.");
return;
var f = argv[0];
if (argv.length === 0) {
Cmdline.print_order("clean");
return;
}
if (!io.exists(f)) {
say(`File ${f} does not exist.`);
return;
}
prototypes.generate_ur();
var j = json.decode(io.slurp(f));
for (var k in j)
if (k in j.objects)
delete j[k];
console.warn(j);
for (var k in j.objects) {
var o = j.objects[k];
samediff(o, ur[o.ur]);
}
say(j);
}, "Clean up a given object file.", "JSON ...");
Cmdline.register_cmd("l", function(n) {

View file

@ -196,3 +196,5 @@ var Tween = {
};
Tween.make = Tween.start;
return {Tween, Ease};

View file

@ -212,8 +212,9 @@ double js2number(JSValue v) {
void *js2ptr(JSValue v) { return JS_GetOpaque(v,js_ptr_id); }
JSValue float2js(double g) { return JS_NewFloat64(js, g);}
JSValue number2js(double g) { return float2js(g); }
JSValue number2js(double g) {
return JS_NewFloat64(js,g);
}
struct sprite *js2sprite(JSValue v) { return id2sprite(js2int(v)); }
JSValue ptr2js(void *ptr) {
@ -1085,6 +1086,11 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
ret = number2js(get_timescale());
break;
case 122:
str = JS_ToCString(js, argv[1]);
ret = file_eval_env(str, argv[2]);
break;
case 123:
str = JS_ToCString(js, argv[1]);
str2 = JS_ToCString(js, argv[3]);
@ -1137,7 +1143,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
break;
case 135:
ret = float2js(cam_zoom());
ret = number2js(cam_zoom());
break;
case 136:

View file

@ -117,7 +117,6 @@ char *nota_write_int(long long n, char *nota)
return nota_continue_num(n, nota, 3);
}
#define NOTA_DBL_PREC 6
#define xstr(s) str(s)
#define str(s) #s

View file

@ -11,9 +11,10 @@
#define NOTA_FALSE 0x00
#define NOTA_TRUE 0x01
#define NOTA_NULL 0x02
#define NOTA_INF 0x03
#define NOTA_PRIVATE 0x08
#define NOTA_SYSTEM 0x09
#define NOTA_NULL 0x02
typedef struct NOTA {
char *head;

View file

@ -148,10 +148,8 @@ void *cdb_slurp(struct cdb *cdb, const char *file, size_t *size)
int fexists(const char *path)
{
return !access(path,R_OK);
int len = strlen(path);
if (cdb_find(&game_cdb, path,len)) return 1;
if (cdb_find(&game_cdb, path, len)) return 1;
else if (cdb_find(&corecdb, path, len)) return 1;
else if (!access(path, R_OK)) return 1;

View file

@ -58,7 +58,7 @@ void script_startup() {
rt = JS_NewRuntime();
js = JS_NewContext(rt);
sh_new_arena(jsstrs);
sh_new_arena(jsstrs);
ffi_load();
@ -231,7 +231,7 @@ JSValue eval_file_env(const char *script, const char *file, JSValue env)
return v;
}
void file_eval_env(const char *file, JSValue env)
JSValue file_eval_env(const char *file, JSValue env)
{
size_t len;
char *script = slurp_text(file, &len);

View file

@ -48,7 +48,7 @@ void call_callee(struct callee *c);
void script_callee(struct callee c, int argc, JSValue *argv);
int script_has_sym(void *sym);
void script_eval_w_env(const char *s, JSValue env, const char *file);
void file_eval_env(const char *file, JSValue env);
JSValue file_eval_env(const char *file, JSValue env);
time_t file_mod_secs(const char *file);

View file

@ -4,16 +4,6 @@
static warp_gravity **warps = NULL;
warp_gravity *warp_gravity_make()
{
warp_gravity *n = calloc(sizeof(*n),1);
n->strength = 9.8;
n->t.scale = (HMM_Vec3){0,-1,0};
n->planar_force = HMM_MulV3F(n->t.scale, n->strength);
arrput(warps, n);
return n;
}
warp_damp *warp_damp_make()
{
warp_damp *d = calloc(sizeof(*d),1);
@ -21,6 +11,17 @@ warp_damp *warp_damp_make()
}
void warp_damp_free(warp_damp *d) { free(d); }
warp_gravity *warp_gravity_make()
{
warp_gravity *n = calloc(sizeof(*n),1);
n->strength = 9.8;
n->t.scale = (HMM_Vec3){0,-1,0};
n->planar_force = (HMM_Vec3){0,-1,0};
arrput(warps, n);
return n;
}
void warp_gravity_free(warp_gravity *n) {
for (int i = 0; i < arrlen(warps); i++) {
if (warps[i] == n) {
@ -47,7 +48,7 @@ HMM_Vec3 warp_gravity_force(warp_gravity *g, HMM_Vec3 pos)
HMM_Vec3 norm = HMM_NormV3(HMM_SubV3(g->t.pos, pos));
return HMM_MulV3F(norm,g->strength);
} else {
return g->planar_force;
return HMM_MulV3F(g->planar_force, g->strength);
}
}

View file

@ -44,5 +44,4 @@ warp_damp *warp_damp_make();
void warp_gravity_free(warp_gravity *g);
void warp_damp_free(warp_damp *d);
#endif