changed make to spawn; unified actor system for entities
This commit is contained in:
parent
de74b42be2
commit
1e432346ff
|
@ -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 |
|
||||
| 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
72
scripts/actor.js
Normal 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};
|
|
@ -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};
|
||||
|
|
|
@ -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
19
scripts/camera2d.jso
Normal 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);
|
||||
},
|
||||
});
|
|
@ -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'],
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 an entity
|
||||
text can be:
|
||||
the file path of a script
|
||||
an ur object
|
||||
nothing
|
||||
*/
|
||||
spawn(text) {
|
||||
var ent = Object.create(gameobject);
|
||||
|
||||
spawn(ur, data) {
|
||||
ur ??= gameobject;
|
||||
if (typeof ur === 'string') {
|
||||
//ur = prototypes.get_ur(ur);
|
||||
if (typeof text === 'object')
|
||||
text = text.name;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var go = ur.make(this, data);
|
||||
Object.hide(this, go.toString());
|
||||
return go;
|
||||
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);
|
||||
},
|
||||
|
||||
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)
|
||||
|
@ -609,9 +625,8 @@ 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 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
|
||||
}
|
||||
*/
|
||||
|
||||
/* Apply an ur u to an entity e */
|
||||
/* u is given as */
|
||||
function apply_ur(u, e)
|
||||
{
|
||||
var path = ur.replaceAll('.', '/');
|
||||
console.warn(path);
|
||||
console.warn(io.exists(path));
|
||||
console.warn(`${path} does not exist; sending ${path.dir()}`);
|
||||
if (typeof u !== 'string') {
|
||||
console.warn("Must give u as a string.");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
prototypes.ur_json = function(ur)
|
||||
function file2fqn(file)
|
||||
{
|
||||
var path = ur.replaceAll('.', '/');
|
||||
if (io.exists(path))
|
||||
path = path + "/" + path.name() + ".json";
|
||||
else
|
||||
path = path + ".json";
|
||||
var fqn = file.strip_ext();
|
||||
if (fqn.folder_same_name())
|
||||
fqn = fqn.up_path();
|
||||
|
||||
return 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];
|
||||
}
|
||||
|
||||
prototypes.ur_stem = function(ur)
|
||||
{
|
||||
var path = ur.replaceAll('.', '/');
|
||||
if (io.exists(path))
|
||||
return path + "/" + path.name();
|
||||
else
|
||||
return path;
|
||||
/* FIND ALL URS IN A PROJECT */
|
||||
for (var file of io.glob("**.jso")) {
|
||||
var topur = file2fqn(file);
|
||||
topur.text = file;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* prototypes.ur_file_exts.forEach(function(e) {
|
||||
var p = stem + e;
|
||||
if (io.exists(p))
|
||||
*/
|
||||
for (var file of io.glob("**.json")) {
|
||||
var topur = file2fqn(file);
|
||||
topur.data = file;
|
||||
}
|
||||
|
||||
return {
|
||||
gameobject,
|
||||
actor,
|
||||
prototypes,
|
||||
ur
|
||||
}
|
||||
|
|
|
@ -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
119
scripts/render.js
Normal 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};
|
|
@ -113,3 +113,5 @@ Object.mixin(cmd(180).__proto__, {
|
|||
pct() { return this.time()/this.length(); },
|
||||
});
|
||||
*/
|
||||
|
||||
return {audio};
|
||||
|
|
112
scripts/spline.js
Normal file
112
scripts/spline.js
Normal 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};
|
|
@ -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) {
|
||||
|
|
|
@ -196,3 +196,5 @@ var Tween = {
|
|||
};
|
||||
|
||||
Tween.make = Tween.start;
|
||||
|
||||
return {Tween, Ease};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,5 +44,4 @@ warp_damp *warp_damp_make();
|
|||
void warp_gravity_free(warp_gravity *g);
|
||||
void warp_damp_free(warp_damp *d);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue