many fixes to editor

This commit is contained in:
John Alanbrook 2024-04-04 17:28:11 -05:00
parent 32268fc25d
commit 403771d7f8
17 changed files with 183 additions and 120 deletions

View file

@ -216,6 +216,15 @@ The ur[fn::A German prefix meaning primitive, original, or earliest.] system is
When prosperon starts, it searches for urs by name. Any file ending in ".jso" or ".json" will be interpereted as an ur, with same named jso and json being applied as (text, config) for an ur. A jso or json alone also constitute an ur.
An ur can also be defined by a json file. If an ur is found, it takes predecent over auto generated urs. The json of an ur looks like this:
| field | description |
|----|----|
| text | Path to a script file, or array of script files, to apply to the object |
| data | Path to a json file, or array of json files, to apply to the object |
Any ur file with this sort of json creates an ur which can be created in the game. A file named "box.ur" will be ingested and be available as "ur.box". When saving differences, it creates a json file with the same name as an ur (in this case, "box.json").
#+begin_scholium
Create an ur from the *hello* files above, and then spawn it.
#+begin_src
@ -225,6 +234,10 @@ 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
This method allows high composability of game objects.
If an entity is created without an ur, is ur is defined as its given text and data. It cannot be saved. It must be given a new ur name.
Each ur has the following fields.
| field | description |

View file

@ -882,6 +882,11 @@ Object.defineProperty(String.prototype, 'uc', { value: function() { return this.
Object.defineProperty(String.prototype, 'lc', {value:function() { return this.toLowerCase(); }});
/* ARRAY DEFS */
Object.defineProperty(Array.prototype, 'aspect', {
value: function() {
return this.x/this.y;
}
});
Object.defineProperty(Array.prototype, 'copy', {
value: function() {
var c = [];

View file

@ -1,20 +1,35 @@
this.phys = physics.kinematic;
this.dir_view2world = function(dir) { return dir.scale(this.zoom); };
this.view2world = function(pos) {
var useren = window.rendersize.scale(this.zoom);
if (window.mode === window.modetypes.stretch) {
pos = pos.scale([window.rendersize.x/window.size.x, window.rendersize.y/window.size.y]);
pos = pos.sub(window.rendersize.scale(0.5));
pos = pos.scale(this.zoom);
pos = pos.add(this.pos);
}
if (window.mode === window.modetypes.full) {
pos = pos.sub(window.size.scale(0.5));
pos = pos.scale(this.zoom);
pos = pos.add(this.pos);
}
return pos;
};
this.world2view = function(pos) {
if (window.mode === window.modetypes.stretch) {
pos = pos.sub(this.pos);
pos = pos.scale(1.0/this.zoom);
pos = pos.add(window.rendersize.scale(0.5));
}
if (window.mode === window.modetypes.full) {
pos = pos.sub(this.pos);
pos = pos.scale(1/this.zoom);
pos = pos.add(window.size.scale(0.5));
}
return pos;
};
this.screenright = function() { return this.view2world(window.size).x; }
this.screenleft = function() { return this.pos.x - (window.rendersize.x/2); }
this.screenleft = function() { return this.view2world([0,0]).x; }
this.zoom = 1;

View file

@ -425,7 +425,7 @@ component.polygon2d = Object.copy(collider2d, {
gizmo() {
this.spoints().forEach(x => render.point(this.gameobject.this2screen(x), 3, Color.green));
this.points.forEach((x,i)=>render.coordinate(this.gameobject.this2screen(x), i));
this.points.forEach((x,i)=>render.coordinate(this.gameobject.this2screen(x)));
},
pick(pos) {
@ -589,11 +589,11 @@ component.edge2d = Object.copy(collider2d, {
this.points.forEach((x,i) => render.coordinate(this.gameobject.this2screen(x)));
} else {
for (var i = 0; i < this.points.length; i += 3)
render.coordinate(this.gameobject.this2screen(this.points[i]), i, Color.teal);
render.coordinate(this.gameobject.this2screen(this.points[i]), 1, Color.teal);
for (var i = 1; i < this.points.length; i+=3) {
render.coordinate(this.gameobject.this2screen(this.points[i]), i, Color.green);
render.coordinate(this.gameobject.this2screen(this.points[i+1]), i+1, Color.green);
render.coordinate(this.gameobject.this2screen(this.points[i]), 1, Color.green);
render.coordinate(this.gameobject.this2screen(this.points[i+1]), 1, Color.green);
render.line([this.gameobject.this2screen(this.points[i-1]), this.gameobject.this2screen(this.points[i])], Color.yellow);
render.line([this.gameobject.this2screen(this.points[i+1]), this.gameobject.this2screen(this.points[i+2])], Color.yellow);
}
@ -707,7 +707,6 @@ component.edge2d.impl = Object.mix(collider2d.impl, {
var bucket = component.edge2d;
bucket.spoints.doc = "Returns the controls points after modifiers are applied, such as it being hollow or mirrored on its axises.";
bucket.inputs = {};
//bucket.inputs.post = function() { this.sync(); };
bucket.inputs.h = function() { this.hollow = !this.hollow; };
bucket.inputs.h.doc = "Toggle hollow.";

View file

@ -3,7 +3,6 @@
selectable
*/
window.mode = window.modetypes.full;
game.loadurs();
console.info(`window size: ${window.size}, render size: ${window.rendersize}`);
@ -11,6 +10,8 @@ console.info(`window size: ${window.size}, render size: ${window.rendersize}`);
player[0].control(debug);
Register.gui.register(debug.draw, debug);
var show_frame = true;
var editor = {
toString() { return "editor"; },
grid_size: 100,
@ -38,8 +39,7 @@ var editor = {
return this.do_select(go);
},
/* Tries to select id */
do_select(obj) {
do_select(obj) { /* select an object, if it is selectable given the current editor state */
if (!obj) return;
if (!obj._ed.selectable) return undefined;
@ -394,13 +394,16 @@ var editor = {
gui() {
/* Clean out killed objects */
if (show_frame)
render.line(shape.box(window.rendersize.x, window.rendersize.y).wrapped(1).map(p => game.camera.world2view(p)), Color.yellow);
render.text([0,0], game.camera.world2view([0,0]));
render.text("WORKING LAYER: " + this.working_layer, [0,520]);
render.text("MODE: " + this.edit_mode, [0,500]);
if (this.comp_info && this.sel_comp)
render.text(Input.print_pawn_kbm(this.sel_comp,false), [100,700],1);
render.text(input.print_pawn_kbm(this.sel_comp,false), [100,700],1);
render.cross(editor.edit_level.screenpos(),3,Color.blue);
@ -530,7 +533,6 @@ var editor = {
var obj = editor.edit_level.spawn(mur);
obj.set_worldpos(input.mouse.worldpos());
this.selectlist = [obj];
console.warn(`made something and now the selected objects is ${this.selectlist.length} long.`);
},
load_prev() {
@ -567,7 +569,7 @@ var editor = {
ur[sub] = {
name: sub,
data: file,
proto: json.decode(json.encode(obj))
fresh: json.decode(json.encode(obj))
}
obj.ur = sub;
@ -667,7 +669,8 @@ editor.inputs.f9 = function() { os.capture( "capture.bmp", 0, 0, 500, 500); }
editor.inputs.release_post = function() {
editor.snapshot();
editor.edit_level.check_dirty();
editor.selectlist?.forEach(x => x.check_dirty());
/* snap all objects to be pixel perfect */
game.all_objects(o => o.pos = o.pos.map(x => Math.round(x)), editor.edit_level);
@ -765,11 +768,11 @@ editor.inputs.m = function() {
};
editor.inputs.m.doc = "Mirror selected objects on the X axis.";
editor.inputs.q = function() { editor.comp_info = !editor.comp_info; };
editor.inputs.q.doc = "Toggle help for the selected component.";
editor.inputs.f = function() {
return;
if (editor.selectlist.length === 0) return;
var bb = editor.selectlist[0].boundingbox();
editor.selectlist.forEach(function(obj) { bb = bbox.expand(bb, obj.boundingbox()); });
@ -787,6 +790,7 @@ editor.inputs['C-f'] = function() {
editor.inputs['C-f'].doc = "Tunnel into the selected level object to edit it.";
editor.inputs['C-F'] = function() {
console.info("PRESSED C-F");
if (editor.edit_level.master === world) return;
editor.edit_level = editor.edit_level.master;
@ -901,14 +905,13 @@ editor.inputs['C-s'] = function() {
if (!tur.data) {
io.slurpwrite(tur.text.set_ext(".json"), json.encode(savejs,null,1));
tur.data = tur.text.set_ext(".json");
}
else {
} else {
var oldjs = json.decode(io.slurp(tur.data));
Object.merge(oldjs, savejs);
io.slurpwrite(tur.data, json.encode(oldjs,null,1));
}
Object.merge(tur.proto, savejs);
Object.merge(tur.fresh, savejs);
saveobj.check_dirty();
// Object.values(saveobj.objects).forEach(function(x) { x.check_dirty(); });
@ -1484,6 +1487,7 @@ var inputpanel = {
};
inputpanel.inputs = {};
inputpanel.inputs.block = true;
inputpanel.inputs.post = function() { this.keycb(); }

View file

@ -241,7 +241,7 @@ global.mixin("scripts/render");
global.mixin("scripts/debug");
var frame_t = profile.secs(profile.now());
var phys_step = 1/60;
var phys_step = 1/240;
var sim = {};
sim.mode = "play";
@ -280,7 +280,6 @@ function process()
prosperon.update(dt*game.timescale);
if (sim.mode === "step")
sim.pause();
}
physlag += dt;
@ -289,6 +288,7 @@ function process()
prosperon.phys2d_step(phys_step*game.timescale);
prosperon.physupdate(phys_step*game.timescale);
}
}
if (!game.camera)
prosperon.window_render(world, 1);
@ -312,15 +312,15 @@ function process()
game.timescale = 1;
game.all_objects = function(fn, startobj) {
startobj ??= world;
var eachobj = function(obj,fn)
{
var eachobj = function(obj,fn)
{
fn(obj);
for (var o in obj.objects)
eachobj(obj.objects[o],fn);
}
}
game.all_objects = function(fn, startobj) {
startobj ??= world;
eachobj(startobj,fn);
};
@ -529,11 +529,13 @@ var Event = {
},
};
// window.rendersize is the resolution the game renders at
// window.size is the physical size of the window on the desktop
window.modetypes = {
stretch: 0, // stretch to fill window
keep: 1, // keep exact dimensions
width: 2, // keep width
height: 3, // keep height
stretch: 0, // stretch render to fill window
keep: 1, // keep render exact dimensions, with no stretching
width: 2, // keep render at width
height: 3, // keep render at height
expand: 4, // expand width or height
full: 5 // expand out beyond window
};
@ -546,8 +548,6 @@ global.mixin("scripts/spline");
global.mixin("scripts/components");
window.doc = {};
window.doc.width = "Width of the game window.";
window.doc.height = "Height of the game window.";
window.doc.dimensions = "Window width and height packaged in an array [width,height]";
window.doc.title = "Name in the title bar of the window.";
window.doc.boundingbox = "Boundingbox of the window, with top and right being its height and width.";
@ -557,7 +557,6 @@ global.mixin("scripts/entity");
function world_start() {
globalThis.world = os.make_gameobject();
world.setref(world);
world.objects = {};
world.toString = function() { return "world"; };
world.ur = "world";

View file

@ -10,8 +10,6 @@ function obj_unique_name(name, obj) {
return n;
}
var urcache = {};
var gameobject_impl = {
get pos() {
assert(this.master, `Entity ${this.toString()} has no master.`);
@ -74,7 +72,7 @@ var gameobject = {
this._ed.urdiff = this.json_obj();
this._ed.dirty = !Object.empty(this._ed.urdiff);
return; // TODO: IMPLEMENT
var lur = ur[this.master.ur];
var lur = this.master.ur;
if (!lur) return;
var lur = lur.objects[this.toString()];
var d = ediff(this._ed.urdiff, lur);
@ -93,8 +91,9 @@ var gameobject = {
},
urstr() {
if (this._ed.dirty) return "*" + this.ur;
return this.ur;
var str = this.ur.name;
if (this._ed.dirty) str = "*" + str;
return str;
},
full_path() {
@ -213,11 +212,7 @@ var gameobject = {
},
cry(file) {
this.crying = audio.play(file, audio.bus.sfx);
var killfn = () => { this.crying = undefined;
console.warn("killed"); }
this.crying.hook = killfn;
return killfn;
return audio.cry(file);
},
gscale() { return this.scale; },
@ -239,7 +234,7 @@ var gameobject = {
worldangle() { return this.angle; },
sworldangle(x) { this.angle = x; },
get_ur() { return urcache[this.ur]; },
get_ur() { return this.ur; },
/* spawn an entity
text can be:
@ -250,24 +245,33 @@ var gameobject = {
spawn(text, config, callback) {
var ent = os.make_gameobject();
ent.guid = prosperon.guid();
ent.setref(ent);
ent.components = {};
ent.objects = {};
ent.timers = [];
if (typeof text === 'object' && text) // assume it's an ur
{
config = text.data;
ent.ur = text.name;
text = text.text;
}
ent.ur = text;
else
ent.ur = getur(text, config);
if (text)
text = ent.ur.text;
config = [ent.ur.data, config];
if (typeof text === 'string')
use(text, ent);
if (config)
Object.assign(ent, json.decode(io.slurp(config)));
else if (Array.isArray(text))
text.forEach(path => use(path,ent));
if (typeof config === 'string')
Object.assign(ent, json.decode(Resources.replstrs(config)));
else if (Array.isArray(config))
config.forEach(function(path) {
if (typeof path === 'string')
Object.assign(ent, json.decode(Resources.replstrs(path)));
else if (typeof path === 'object')
Object.assign(ent,path);
});
ent.ur ??= text + "+" + config;
ent.reparent(this);
for (var [prop, p] of Object.entries(ent)) {
@ -296,8 +300,7 @@ var gameobject = {
selectable: true,
dirty: false,
inst: false,
urdiff: {},
fresh: json.decode(json.encode(ent)),
urdiff: {}
};
Object.hide(ent, '_ed');
@ -308,12 +311,10 @@ var gameobject = {
var o = ent.objects;
delete ent.objects;
for (var i in o) {
console.info(`MAKING ${i}`);
var n = ent.spawn(ur[o[i].ur]);
ent.rename_obj(n.toString(), i);
var newur = o[i].ur;
delete o[i].ur;
Object.assign(n, o[i]);
n.sync();
var n = ent.spawn(ur[newur], o[i]);
ent.rename_obj(n.toString(), i);
}
}
@ -321,6 +322,8 @@ var gameobject = {
if (callback) callback(ent);
ent.ur.fresh ??= json.decode(json.encode(ent));
return ent;
},
@ -349,7 +352,7 @@ var gameobject = {
return t;
};
var name = unique_name(Object.keys(parent.objects), this.ur);
var name = unique_name(Object.keys(parent.objects), this.ur.name);
parent.objects[name] = this;
parent[name] = this;
@ -446,7 +449,7 @@ var gameobject = {
/* The unique components of this object. Its diff. */
json_obj() {
var fresh = this._ed.fresh;
var fresh = this.ur.fresh;
var thiso = json.decode(json.encode(this)); // TODO: SLOW. Used to ignore properties in toJSON of components.
var d = ediff(thiso, fresh);
@ -473,7 +476,7 @@ var gameobject = {
/* The object needed to store an object as an instance of a master */
instance_obj() {
var t = this.transform();
t.ur = this.ur;
t.ur = this.ur.name;
return t;
},
@ -484,7 +487,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._ed.fresh.scale[i]);
t.scale = t.scale.map((x, i) => x / this.ur.fresh.scale[i]);
t.scale = t.scale.map(x => Math.places(x, 3));
if (t.scale.every(x => x === 1)) delete t.scale;
return t;
@ -499,7 +502,7 @@ var gameobject = {
},
dup(diff) {
var n = this.master.spawn(this.__proto__);
var n = this.master.spawn(this.ur);
Object.totalmerge(n, this.instance_obj());
return n;
},
@ -592,9 +595,8 @@ var gameobject = {
function go_init() {
var gop = os.make_gameobject().__proto__;
Object.mixin(gop, gameobject);
var gsync = gop.sync;
gop.sync = function() {
gsync.call(this);
this.selfsync();
this.components.forEach(function(x) { x.sync?.(); });
this.objects.forEach(function(x) { x.sync?.(); });
}
@ -734,10 +736,33 @@ function file2fqn(file) {
return ur[fqn];
}
var getur = function(text, data)
{
var urstr = text + "+" + data;
if (!ur[urstr]) {
ur[urstr] = {
name: urstr,
text: text,
data: data
}
}
return ur[urstr];
}
game.loadurs = function() {
ur = {};
ur._list = [];
/* FIND ALL URS IN A PROJECT */
for (var file of io.glob("**.ur")) {
var urname = file.name();
if (ur[urname]) {
console.warn(`Tried to make another ur with the name ${urname} from ${file}, but it already exists.`);
continue;
}
var urjson = json.decode(io.slurp(file));
urjson.name = urname;
ur[urname] = urjson;
}
for (var file of io.glob("**.jso")) {
if (file[0] === '.' || file[0] === '_') continue;
var topur = file2fqn(file);

View file

@ -1,4 +1,5 @@
var audio = {};
var cries = {};
audio.samplerate = dspsound.samplerate();
audio.play = function(file,bus) {
@ -10,6 +11,7 @@ audio.play = function(file,bus) {
}
var src = audio.dsp.source(file);
src.plugin(bus);
src.guid = prosperon.guid();
return src;
}
audio.bus = {};
@ -17,15 +19,24 @@ audio.bus.master = dspsound.master();
audio.dsp = {};
audio.dsp = dspsound;
var cries = [];
audio.cry = function(file)
{
var player = audio.play(file);
cries.push(player);
var hook = function() { console.warn("ENDED SOUND!!!!"); cries.remove(player); player = undefined; hook = undefined;}
player.hook = hook;
player.guid = prosperon.guid();
cries[player.guid] = player;
player.ended = function() { delete cries[player.guid]; player = undefined; }
return player.ended;
}
var killer = Register.appupdate.register(function() {
for (var i in cries) {
var cry = cries[i];
if (!cry.ended) continue;
if (cry.frame < cry.lastframe || cry.frame === cry.lastframe) cry.ended();
cry.lastframe = cry.frame;
}
});
var song;
audio.music = function(file, fade) {

View file

@ -193,9 +193,13 @@ Cmdline.register_order("edit", function() {
window.size = [1280, 720];
window.mode = window.modetypes.full;
sim.pause();
game.engine_start(function() {
global.mixin("scripts/editor.js");
use("editorconfig.js");
use("config.js");
editor.enter_editor();
});
}, "Edit the project in this folder. Give it the name of an UR to edit that specific object.", "?UR?");

View file

@ -530,7 +530,6 @@ int shape_get_sensor(struct phys2d_shape *shape) {
if (!shape->shape) {
struct phys2d_edge *edge = shape->data;
if (arrlen(edge->shapes) > 0) return cpShapeGetSensor(edge->shapes[0]);
YughWarn("Attempted to get the sensor of an edge with no shapes. It has %d points.", arrlen(edge->points));
return 0;
}

View file

@ -244,7 +244,7 @@ void text_flush(HMM_Mat4 *proj) {
}
void sdrawCharacter(struct Character c, HMM_Vec2 cursor, float scale, struct rgba color) {
if (curchar+1 >= max_chars)
if (curchar-10 >= max_chars)
return;
struct rgba colorbox = {0,0,0,255};

View file

@ -61,10 +61,6 @@ const char *js2str(JSValue v) {
return JS_ToCString(js, v);
}
/* support functions for these macros when they involve a JSValue */
JSValue js2JSValue(JSValue v) { return v; }
JSValue JSValue2js(JSValue v) { return v; }
QJSCLASS(gameobject)
QJSCLASS(emitter)
QJSCLASS(dsp_node)
@ -1082,14 +1078,12 @@ static const JSCFunctionListEntry js_dsp_node_funcs[] = {
JSC_GETSET(sound, loop, boolean)
JSC_GETSET(sound, timescale, number)
JSC_GETSET(sound, frame, number)
JSC_GETSET_HOOK(sound, hook)
JSC_CCALL(sound_frames, return number2js(js2sound(this)->data->frames))
static const JSCFunctionListEntry js_sound_funcs[] = {
CGETSET_ADD(sound, loop),
CGETSET_ADD(sound, timescale),
CGETSET_ADD(sound, frame),
CGETSET_ADD(sound, hook),
MIST_FUNC_DEF(sound, frames, 0),
};
@ -1164,8 +1158,7 @@ JSC_GETSET(gameobject, maxvelocity, number)
JSC_GETSET(gameobject, maxangularvelocity, number)
JSC_GETSET(gameobject, warp_filter, bitmask)
JSC_GETSET(gameobject, drawlayer, number)
JSC_CCALL(gameobject_setref, js2gameobject(this)->ref = argv[0]);
JSC_CCALL(gameobject_sync, gameobject_apply(js2gameobject(this)))
JSC_CCALL(gameobject_selfsync, gameobject_apply(js2gameobject(this)))
JSC_CCALL(gameobject_in_air, return boolean2js(phys2d_in_air(js2gameobject(this)->body)))
JSC_CCALL(gameobject_world2this, return vec22js(world2go(js2gameobject(this), js2vec2(argv[0]))))
JSC_CCALL(gameobject_this2world, return vec22js(go2world(js2gameobject(this), js2vec2(argv[0]))))
@ -1200,8 +1193,7 @@ static const JSCFunctionListEntry js_gameobject_funcs[] = {
MIST_FUNC_DEF(gameobject, this2world, 1),
MIST_FUNC_DEF(gameobject, dir_world2this, 1),
MIST_FUNC_DEF(gameobject, dir_this2world, 1),
MIST_FUNC_DEF(gameobject,setref,1),
MIST_FUNC_DEF(gameobject, sync, 0),
MIST_FUNC_DEF(gameobject, selfsync, 0),
};
JSC_CCALL(joint_pin, return constraint2js(constraint_make(cpPinJointNew(js2gameobject(argv[0])->body, js2gameobject(argv[1])->body, cpvzero,cpvzero))))
@ -1454,6 +1446,7 @@ JSC_CCALL(os_sprite,
JSC_CCALL(os_make_gameobject,
ret = gameobject2js(MakeGameobject());
JS_SetPropertyFunctionList(js, ret, js_gameobject_funcs, countof(js_gameobject_funcs));
js2gameobject(ret)->ref = ret;
return ret;
)
JSC_CCALL(os_make_circle2d,

View file

@ -278,13 +278,11 @@ void sound_fillbuf(struct sound *s, soundbyte *buf, int n) {
if(end) {
if (s->loop)
s->frame = 0;
script_call_sym(s->hook,0,NULL);
}
}
void free_source(struct sound *s)
{
JS_FreeValue(js, s->hook);
src_delete(s->src);
free(s);
}
@ -305,7 +303,6 @@ struct dsp_node *dsp_source(const char *path)
self->loop = false;
self->src = src_callback_new(src_cb, SRC_SINC_MEDIUM_QUALITY, 2, NULL, self);
self->timescale = 1;
self->hook = JS_UNDEFINED;
dsp_node *n = make_node(self, sound_fillbuf, free_source);
return n;
}

View file

@ -17,7 +17,6 @@ typedef struct sound {
int loop;
float timescale;
SRC_STATE *src;
JSValue hook;
} sound;
/* Represents a sound file source, fulled loaded*/

View file

@ -112,7 +112,7 @@ void quickjs_set_dumpout(FILE *f)
//#define DUMP_READ_OBJECT
#ifdef DUMP
#define DUMP_FREE
//#define DUMP_FREE
#define DUMP_MEM
#define DUMP_CLOSURE
#define DUMP_GC