diff --git a/scripts/base.js b/scripts/base.js index 2e942f2..41e9083 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -110,6 +110,8 @@ Object.merge = function(target, ...objs) for (var obj of objs) for (var key of Object.keys(obj)) Object.mergekey(target,obj,key); + + return target; } Object.totalmerge = function(target, ...objs) @@ -146,6 +148,17 @@ Object.defHidden = function(obj, prop) Object.defineProperty(obj, prop, {enumerable:false, writable:true}); } +Object.hide = function(obj,prop) +{ + var p = Object.getOwnPropertyDescriptor(obj,prop); + if (!p) { + Log.warn(`No property of name ${prop}.`); + return; + } + p.enumerable = false; + Object.defineProperty(obj, prop, p); +} + Object.defineProperty(Object.prototype, 'obscure', { value: function(name) { Object.defineProperty(this, name, { enumerable: false }); @@ -507,7 +520,7 @@ value: function(b) { if (b == null) return false; if (this === b) return true; - return JSON.stringify(this) === JSON.stringify(b); + return JSON.stringify(this.sort()) === JSON.stringify(b.sort()); for (var i = 0; i < this.length; i++) { if (!this[i] === b[i]) @@ -669,7 +682,7 @@ Math.lerp = function(s,f,t) { return (f-s)*t + s; }; Number.prec = function(num) { - return parseFloat(num.toFixed(2)); + return parseFloat(num.toFixed(3)); } Object.defineProperty(Object.prototype, 'lerp',{ diff --git a/scripts/components.js b/scripts/components.js index 00398a1..10f1f05 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -78,8 +78,8 @@ component.sprite.maker = Object.copy(component, { get path() { return this.resavi(cmd(116,this.id)); }, - get visible() { return this.enabled; }, - set visible(x) { this.enabled = x; }, + hide() { this.enabled = false; }, + show() { this.enabled = true; }, asset(str) { this.path = str; }, rect: {s0:0, s1: 1, t0: 0, t1: 1}, get enabled() { return cmd(114,this.id); }, diff --git a/scripts/diff.js b/scripts/diff.js index 37d38d3..dd522d7 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -83,6 +83,11 @@ function diffassign(target, from) { } }; +function positive_diff(from, to) +{ + var diff = {}; +} + function diff(from, to) { var obj = {}; diff --git a/scripts/editor.js b/scripts/editor.js index b618208..37423dd 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -388,41 +388,50 @@ var editor = { GUI.text("0,0", world2screen([0,0])); var clvl = this.edit_level; - var lvlcolorsample = 1; - var lvlcolor = ColorMap.Inferno.sample(lvlcolorsample); + var lvlchain = []; + while (clvl !== Primum) { + lvlchain.push(clvl); + clvl = clvl.level; + } + lvlchain.push(clvl); + + var lvlcolorsample = 1; + var colormap = ColorMap.Bathymetry; + var lvlcolor = colormap.sample(lvlcolorsample); var ypos = 200; - while (clvl) { - lvlcolor = ColorMap.Inferno.sample(lvlcolorsample); - var lvlstr = clvl.toString(); - if (clvl.dirty) + + lvlchain.reverse(); + lvlchain.forEach(function(x) { + lvlcolor = colormap.sample(lvlcolorsample); + var lvlstr = x.toString(); + if (x.dirty) lvlstr += "*"; GUI.text(lvlstr, [0, ypos], 1, lvlcolor); lvlcolorsample -= 0.1; - if (!clvl.level) break; - clvl = clvl.level; - if (clvl) { - GUI.text("^^^^^^", [0,ypos-15],1); - ypos -= 15; - } - ypos -= 5; - } - + GUI.text("^^^^^^", [0,ypos+=5],1); + ypos += 15; + }); + /* Color selected objects with the next deeper color */ lvlcolorsample -= 0.1; - lvlcolor = ColorMap.Inferno.sample(lvlcolorsample); + lvlcolor = colormap.sample(lvlcolorsample); + + GUI.text("$$$$$$", [0,ypos],1,lvlcolor); this.selectlist.forEach(function(x) { var sname = x.ur.toString(); - if (!x.json_obj().empty) + if (!x.level_obj().empty) x.dirty = true; else x.dirty = false; + if (x.dirty) sname += "*"; - GUI.text(sname, world2screen(x.worldpos).add([0, 16]), 1, lvlcolor); - GUI.text(x.worldpos.map(function(x) { return Math.round(x); }), world2screen(x.worldpos), 1, Color.white); - Debug.arrow(world2screen(x.worldpos), world2screen(x.worldpos.add(x.up().scale(40))), Color.yellow, 1); + + GUI.text(sname, world2screen(x.worldpos()).add([0, 16]), 1, lvlcolor); + GUI.text(x.worldpos().map(function(x) { return Math.round(x); }), world2screen(x.worldpos()), 1, Color.white); + Debug.arrow(world2screen(x.worldpos()), world2screen(x.worldpos().add(x.up().scale(40))), Color.yellow, 1); if ('gizmo' in x && typeof x['gizmo'] === 'function' ) x.gizmo(); @@ -431,12 +440,12 @@ var editor = { if (this.selectlist.length === 0) for (var key in this.edit_level.objects) { var o = this.edit_level.objects[key]; - GUI.text(key, world2screen(o.worldpos), 1, lvlcolor); + GUI.text(key, world2screen(o.worldpos()), 1, lvlcolor); } else this.selectlist.forEach(function(x) { Object.entries(x.objects).forEach(function(x) { - GUI.text(x[0], world2screen(x[1].worldpos), 1, lvlcolor); + GUI.text(x[0], world2screen(x[1].worldpos()), 1, lvlcolor); }); }); @@ -450,7 +459,7 @@ var editor = { for (var key in this.selectlist[0].components) { var selected = this.sel_comp === this.selectlist[0].components[key]; var str = (selected ? ">" : " ") + key + " [" + this.selectlist[0].components[key].name + "]"; - GUI.text(str, world2screen(this.selectlist[0].worldpos).add([0,-16*(i++)])); + GUI.text(str, world2screen(this.selectlist[0].worldpos()).add([0,-16*(i++)])); } if (this.sel_comp) { @@ -460,7 +469,7 @@ var editor = { editor.edit_level.objects.forEach(function(obj) { if (!obj.selectable) - GUI.image("icons/icons8-lock-16.png", world2screen(obj.worldpos)); + GUI.image("icons/icons8-lock-16.png", world2screen(obj.worldpos())); }); Debug.draw_grid(1, editor_config.grid_size, Color.Editor.grid.alpha(0.3)); @@ -696,7 +705,7 @@ editor.inputs['C-r'].doc = "Negate the selected's angle."; editor.inputs.r = function() { if (editor.sel_comp && 'angle' in editor.sel_comp) { - var relpos = Mouse.worldpos.sub(editor.sel_comp.gameobject.worldpos); + var relpos = Mouse.worldpos.sub(editor.sel_comp.gameobject.worldpos()); editor.startoffset = Math.atan2(relpos.y, relpos.x); editor.startrot = editor.sel_comp.angle; @@ -779,12 +788,13 @@ editor.inputs['C-s'] = function() { editor.openpanel(saveaspanel); return; }; - + if (editor.selectlist.length !== 1 || !editor.selectlist[0].dirty) return; - Object.merge(editor.selectlist[0].ur_obj(), editor.selectlist[0].level_obj()); + Object.merge(editor.selectlist[0].ur, editor.selectlist[0].level_obj()); var path = editor.selectlist[0].ur.toString(); path = path.replaceAll('.','/'); path = path + "/" + path.name() + ".json"; + IO.slurpwrite(JSON.stringify(editor.selectlist[0].ur,null,1), path); }; editor.inputs['C-s'].doc = "Save selected."; @@ -1385,19 +1395,16 @@ var replpanel = Object.copy(inputpanel, { }, action() { - var ecode = ""; - if (editor.selectlist.length === 1) { - for (var key in editor.selectlist[0].objects) - ecode += `var ${key} = editor.selectlist[0].objects['${key}'];`; - } else { - for (var key in editor.edit_level.objects) - ecode += `var ${key} = editor.edit_level.objects['${key}'];`; - } + var ecode = ""; + var repl_obj = (editor.selectlist.length === 1) ? editor.selectlist[0] : editor.edit_level; + ecode += `var $ = repl_obj.objects;`; + for (var key in repl_obj.objects) + ecode += `var ${key} = editor.edit_level.objects['${key}'];`; ecode += this.value; Log.say(this.value); this.value = ""; - var ret = function() {return eval(ecode);}.call(editor.selectlist[0]); + var ret = function() {return eval(ecode);}.call(repl_obj); Log.say(ret); }, }); diff --git a/scripts/entity.js b/scripts/entity.js index 3923d81..cb01934 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -11,9 +11,6 @@ function grab_from_points(pos, points, slop) { }; var gameobject = { - save: true, - selectable: true, - ed_locked: false, layer_nuke() { Nuke.label("Collision layer"); @@ -29,19 +26,9 @@ var gameobject = { for (var i = 0; i < 5; i++) this.draw_layer = Nuke.radio(i, this.draw_layer, i); }, - - _visible: true, - get visible(){ return this._visible; }, - set visible(x) { - this._visible = x; - for (var key in this.components) { - if ('visible' in this.components[key]) { - this.components[key].visible = x; - } - } - for (var key in this.objects) - this.objects[key].visible = x; - }, + + hide() { this.components.forEach(function(x) { x.hide(); }); this.objects.forEach(function(x) { x.hide(); }); }, + show() { this.components.forEach(function(x) { x.show(); }); this.objects.forEach(function(x) { x.show(); }); }, phys_nuke() { Nuke.newline(1); @@ -66,8 +53,6 @@ var gameobject = { return this.angle - this.level.angle; }, - gizmo: "", /* Path to an image to draw for this gameobject */ - width() { var bb = this.boundingbox(); return bb.r - bb.l; @@ -80,9 +65,9 @@ var gameobject = { /* Make a unique object the same as its prototype */ revert() { - var t = this.transform(); +// var t = this.transform(); Object.totalmerge(this,this.ur); - Object.merge(this,t); +// Object.merge(this,t); }, gui() { @@ -96,7 +81,6 @@ var gameobject = { } }, - check_registers(obj) { Register.unregister_obj(this); @@ -123,8 +107,6 @@ var gameobject = { register_collide(1, x.collide, x, obj.body, x.shape); }); }, - instances: [], - get scale() { return cmd(103, this.body); }, set scale(x) { var pct = x/this.scale; @@ -143,7 +125,7 @@ var gameobject = { this.objects.forEach(function(obj) { obj.flipx = !obj.flipx; var rp = obj.pos; - obj.pos = [-rp.x, rp.y].add(this.worldpos); + obj.pos = [-rp.x, rp.y].add(this.worldpos()); obj.angle = -obj.angle; },this); }, @@ -154,22 +136,21 @@ var gameobject = { return; this.objects.forEach(function(obj) { var rp = obj.pos; - obj.pos = [rp.x, -rp.y].add(this.worldpos); + obj.pos = [rp.x, -rp.y].add(this.worldpos()); obj.angle = -obj.angle; },this); }, - set pos(x) { - this.worldpos = Vector.rotate(x, Math.deg2rad(this.level.angle)).add(this.level.worldpos); }, + set pos(x) { this.set_worldpos(Vector.rotate(x, Math.deg2rad(this.level.angle)).add(this.level.worldpos())); }, get pos() { - var offset = this.worldpos.sub(this.level.worldpos); + var offset = this.worldpos().sub(this.level.worldpos()); return Vector.rotate(offset, -Math.deg2rad(this.level.angle)); }, - get worldpos() { return q_body(1,this.body); }, - set worldpos(x) { - var diff = x.sub(this.worldpos); - this.objects.forEach(function(x) { x.worldpos = x.worldpos.add(diff); }); + worldpos() { return q_body(1,this.body); }, + set_worldpos(x) { + var diff = x.sub(this.worldpos()); + this.objects.forEach(function(x) { x.set_worldpos(x.worldpos().add(diff)); }); set_body(2,this.body,x); }, @@ -209,6 +190,7 @@ var gameobject = { set velocity(x) { set_body(9, this.body, x); }, get angularvelocity() { return Math.rad2deg(q_body(4, this.body)); }, set angularvelocity(x) { set_body(8, this.body, Math.deg2rad(x)); }, + pulse(vec) { set_body(4, this.body, vec);}, push(vec) { set_body(12,this.body,vec);}, @@ -269,52 +251,52 @@ var gameobject = { return bb ? bb : cwh2bb([0,0], [0,0]); }, - json_obj() { - function objdiff(from, to) { - var ret = {}; + diff(from, to) { + var ret = {}; - for (var key in from) { - if (typeof from[key] === 'undefined' || typeof to[key] === 'undefined') continue; - if (typeof from[key] === 'function') continue; - if (typeof to === 'object' && !(key in to)) continue; + for (var key in from) { + if (!Object.hasOwn(from, key) && !Object.isAccessor(from,key)) continue; + if (typeof from[key] === 'undefined' || typeof to[key] === 'undefined') continue; + if (typeof from[key] === 'function') continue; +// if (typeof to === 'object' && !(key in to)) continue; - if (Array.isArray(from[key])) { - if (!Array.isArray(to[key])) - ret[key] = Object.values(objdiff(from[key], [])); + if (Array.isArray(from[key])) { + if (!Array.isArray(to[key])) + ret[key] = Object.values(gameobject.diff(from[key], [])); - if (from[key].length !== to[key].length) - ret[key] = Object.values(objdiff(from[key], [])); + if (from[key].length !== to[key].length) + ret[key] = Object.values(gameobject.diff(from[key], [])); - var diff = objdiff(from[key], to[key]); - if (diff && !diff.empty) - ret[key] = Object.values(diff); + var diff = gameobject.diff(from[key], to[key]); + if (diff && !diff.empty) + ret[key] = Object.values(diff); - continue; - } - - if (typeof from[key] === 'object') { - var diff = objdiff(from[key], to[key]); - if (diff && !diff.empty) - ret[key] = diff; - continue; - } - - if (typeof from[key] === 'number') { - var a = Number.prec(from[key]); - if (a !== to[key]) - ret[key] = a; - continue; - } - - if (from[key] !== to[key]) - ret[key] = from[key]; + continue; } - if (ret.empty) return undefined; - return ret; + + if (typeof from[key] === 'object') { + var diff = gameobject.diff(from[key], to[key]); + if (diff && !diff.empty) + ret[key] = diff; + continue; + } + + if (typeof from[key] === 'number') { + var a = Number.prec(from[key]); + if (a !== to[key]) + ret[key] = a; + continue; + } + + if (from[key] !== to[key]) + ret[key] = from[key]; } + if (ret.empty) return undefined; + return ret; + }, - var ur = objdiff(this,this.ur); - + json_obj() { + var ur = gameobject.diff(this,this.ur); return ur ? ur : {}; }, @@ -326,11 +308,23 @@ var gameobject = { level_obj() { var json = this.json_obj(); - Object.entries(this.objects).forEach(function(x) { - json[x[0]] = x[1].transform_obj(); - Object.assign(json[x[0]], x[1].json_obj()); - json[x[0]].ur = x[1].ur.toString(); - }); + var objects = {}; + this.ur.objects ??= {}; + if (!Object.keys(this.objects).equal(Object.keys(this.ur.objects))) { + for (var o in this.objects) + objects[o] = this.objects[o].transform_obj(); + } else { + for (var o in this.objects) { + var obj = this.objects[o].json_obj(); + Object.assign(obj, gameobject.diff(this.objects[o].transform(), this.ur.objects[o])); + if (!obj.empty) + objects[o] = obj; + } + } + + if (!objects.empty) + json.objects = objects; + return json; }, @@ -423,6 +417,8 @@ var gameobject = { obj.body = make_gameobject(); obj.components = {}; obj.objects = {}; + Object.hide(obj, 'components'); + Object.hide(obj, 'objects'); obj.toString = function() { return obj.ur.toString(); }; Game.register_obj(obj); @@ -439,24 +435,52 @@ var gameobject = { if ('ur' in p) { obj[prop] = obj.spawn(prototypes.get_ur(p.ur)); + obj.rename_obj(obj[prop].toString(), prop); } else if ('comp' in p) { obj[prop] = component[p.comp].make(obj); obj.components[prop] = obj[prop]; } }; + if (ur.objects) { + for (var prop in ur.objects) { + var o = ur.objects[prop]; + var newobj = obj.spawn(prototypes.get_ur(o.ur)); + obj.rename_obj(newobj.toString(), prop); + } + } + var save_tostr = obj.toString; Object.totalmerge(obj,ur); obj.toString = save_tostr; - obj.objects.forEach(function(x) { x.pos = obj.pos.add(x.pos); }); obj.components.forEach(function(x) { if ('sync' in x) x.sync(); }); obj.check_registers(obj); + + obj.objects.forEach(function(x) { + x.ur = prototypes.get_ur(x.ur); + }); if (typeof obj.start === 'function') obj.start(); return obj; }, + rename_obj(name, newname) { + if (!this.objects[name]) { + Log.warn(`No object with name ${name}. Could not rename to ${newname}.`); + return; + } + if (this.objects[newname]) { + Log.warn(`Already an object with name ${newname}.`); + return; + } + Log.warn(`Renaming from ${name} to ${newname}.`); + + this.objects[newname] = this.objects[name]; + delete this.objects[name]; + return this.objects[newname]; + }, + register_hit(fn, obj) { if (!obj) obj = this; @@ -474,6 +498,8 @@ var gameobject = { gameobject.toJSON = gameobject.level_obj; +gameobject.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the spawned entity.`; + gameobject.ur = { // pos: [0,0], scale:1, @@ -486,10 +512,11 @@ gameobject.ur = { // velocity:[0,0], // angularvelocity:0, layer: 0, + save: true, + selectable: true, + ed_locked: false, }; -gameobject.entity = {}; - /* Default objects */ var prototypes = {}; prototypes.ur = {};