501 lines
11 KiB
JavaScript
501 lines
11 KiB
JavaScript
|
var Level = {
|
||
|
levels: [],
|
||
|
objects: [],
|
||
|
alive: true,
|
||
|
selectable: true,
|
||
|
toString() {
|
||
|
if (this.file)
|
||
|
return this.file;
|
||
|
|
||
|
return "Loose level";
|
||
|
},
|
||
|
|
||
|
fullpath() {
|
||
|
//return `${this.level.fullpath()}.${this.name}`;
|
||
|
},
|
||
|
|
||
|
get boundingbox() {
|
||
|
return bb_from_objects(this.objects);
|
||
|
},
|
||
|
|
||
|
varname2obj(varname) {
|
||
|
for (var i = 0; i < this.objects.length; i++)
|
||
|
if (this.objects[i].varname === varname)
|
||
|
return this.objects[i];
|
||
|
|
||
|
return null;
|
||
|
},
|
||
|
|
||
|
run() {
|
||
|
// TODO: If an object does not have a varname, give it one based on its parent
|
||
|
this.objects.forEach(function(x) {
|
||
|
if (x.hasOwn('varname')) {
|
||
|
scene[x.varname] = x;
|
||
|
this[x.varname] = x;
|
||
|
}
|
||
|
},this);
|
||
|
|
||
|
var script = IO.slurp(this.scriptfile);
|
||
|
compile_env(`var self = this;${script}`, this, this.scriptfile);
|
||
|
|
||
|
if (typeof this.update === 'function')
|
||
|
Register.update.register(this.update, this);
|
||
|
|
||
|
if (typeof this.gui === 'function')
|
||
|
Register.gui.register(this.gui, this);
|
||
|
|
||
|
if (typeof this.nk_gui === 'function')
|
||
|
register_nk_gui(this.nk_gui, this);
|
||
|
|
||
|
if (typeof this.inputs === 'object') {
|
||
|
Player.players[0].control(this);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
revert() {
|
||
|
delete this.unique;
|
||
|
this.load(this.filelvl);
|
||
|
},
|
||
|
|
||
|
/* Returns how many objects this level created are still alive */
|
||
|
object_count() {
|
||
|
return objects.length();
|
||
|
},
|
||
|
|
||
|
/* Save a list of objects into file, with pos acting as the relative placement */
|
||
|
saveas(objects, file, pos) {
|
||
|
if (!pos) pos = find_com(objects);
|
||
|
|
||
|
objects.forEach(function(obj) {
|
||
|
obj.pos = obj.pos.sub(pos);
|
||
|
});
|
||
|
|
||
|
var newlvl = Level.create();
|
||
|
|
||
|
objects.forEach(function(x) { newlvl.register(x); });
|
||
|
|
||
|
var save = newlvl.save();
|
||
|
slurpwrite(save, file);
|
||
|
},
|
||
|
|
||
|
clean() {
|
||
|
for (var key in this.objects)
|
||
|
clean_object(this.objects[key]);
|
||
|
|
||
|
for (var key in gameobjects)
|
||
|
clean_object(gameobjects[key]);
|
||
|
},
|
||
|
|
||
|
sync_file(file) {
|
||
|
var openlvls = this.levels.filter(function(x) { return x.file === file && x !== editor.edit_level; });
|
||
|
|
||
|
openlvls.forEach(function(x) {
|
||
|
x.clear();
|
||
|
x.load(IO.slurp(x.file));
|
||
|
x.flipdirty = true;
|
||
|
x.sync();
|
||
|
x.flipdirty = false;
|
||
|
x.check_dirty();
|
||
|
});
|
||
|
},
|
||
|
|
||
|
save() {
|
||
|
this.clean();
|
||
|
var pos = this.pos;
|
||
|
var angle = this.angle;
|
||
|
|
||
|
this.pos = [0,0];
|
||
|
this.angle = 0;
|
||
|
if (this.flipx) {
|
||
|
this.objects.forEach(function(obj) {
|
||
|
this.mirror_x_obj(obj);
|
||
|
}, this);
|
||
|
}
|
||
|
|
||
|
if (this.flipy) {
|
||
|
this.objects.forEach(function(obj) {
|
||
|
this.mirror_y_obj(obj);
|
||
|
}, this);
|
||
|
}
|
||
|
|
||
|
var savereturn = JSON.stringify(this.objects, replacer_empty_nil, 1);
|
||
|
|
||
|
if (this.flipx) {
|
||
|
this.objects.forEach(function(obj) {
|
||
|
this.mirror_x_obj(obj);
|
||
|
}, this);
|
||
|
}
|
||
|
|
||
|
if (this.flipy) {
|
||
|
this.objects.forEach(function(obj) {
|
||
|
this.mirror_y_obj(obj);
|
||
|
}, this);
|
||
|
}
|
||
|
|
||
|
this.pos = pos;
|
||
|
this.angle = angle;
|
||
|
return savereturn;
|
||
|
},
|
||
|
|
||
|
mirror_x_obj(obj) {
|
||
|
obj.flipx = !obj.flipx;
|
||
|
var rp = obj.relpos;
|
||
|
obj.pos = [-rp.x, rp.y].add(this.pos);
|
||
|
obj.angle = -obj.angle;
|
||
|
},
|
||
|
|
||
|
mirror_y_obj(obj) {
|
||
|
var rp = obj.relpos;
|
||
|
obj.pos = [rp.x, -rp.y].add(this.pos);
|
||
|
obj.angle = -obj.angle;
|
||
|
},
|
||
|
|
||
|
/* TODO: Remove this; make it work without */
|
||
|
toJSON() {
|
||
|
var obj = {};
|
||
|
obj.file = this.file;
|
||
|
obj.pos = this._pos;
|
||
|
obj.angle = this._angle;
|
||
|
obj.from = "group";
|
||
|
obj.flipx = this.flipx;
|
||
|
obj.flipy = this.flipy;
|
||
|
obj.scale = this.scale;
|
||
|
|
||
|
if (this.varname)
|
||
|
obj.varname = this.varname;
|
||
|
|
||
|
if (!this.unique)
|
||
|
return obj;
|
||
|
|
||
|
obj.objects = {};
|
||
|
|
||
|
this.objects.forEach(function(x,i) {
|
||
|
obj.objects[i] = {};
|
||
|
var adiff = Math.abs(x.relangle - this.filelvl[i]._angle) > 1e-5;
|
||
|
if (adiff)
|
||
|
obj.objects[i].angle = x.relangle;
|
||
|
|
||
|
var pdiff = Vector.equal(x.relpos, this.filelvl[i]._pos, 1e-5);
|
||
|
if (!pdiff)
|
||
|
obj.objects[i].pos = x._pos.sub(this.pos);
|
||
|
|
||
|
if (obj.objects[i].empty)
|
||
|
delete obj.objects[i];
|
||
|
}, this);
|
||
|
|
||
|
return obj;
|
||
|
},
|
||
|
|
||
|
register(obj) {
|
||
|
if (obj.level)
|
||
|
obj.level.unregister(obj);
|
||
|
|
||
|
this.objects.push(obj);
|
||
|
},
|
||
|
|
||
|
make() {
|
||
|
return Level.loadfile(this.file, this.pos);
|
||
|
},
|
||
|
|
||
|
spawn(prefab) {
|
||
|
if (typeof prefab === 'string') {
|
||
|
var newobj = this.addfile(prefab);
|
||
|
return newobj;
|
||
|
}
|
||
|
|
||
|
var newobj = prefab.make();
|
||
|
newobj.defn('level', this);
|
||
|
this.objects.push(newobj);
|
||
|
Game.register_obj(newobj);
|
||
|
newobj.setup?.();
|
||
|
newobj.start?.();
|
||
|
if (newobj.update)
|
||
|
Register.update.register(newobj.update, newobj);
|
||
|
return newobj;
|
||
|
},
|
||
|
|
||
|
dup(level) {
|
||
|
level ??= this.level;
|
||
|
var n = level.spawn(this.from);
|
||
|
/* TODO: Assign this's properties to the dup */
|
||
|
return ;n
|
||
|
},
|
||
|
|
||
|
create() {
|
||
|
var newlevel = Object.create(this);
|
||
|
newlevel.objects = [];
|
||
|
newlevel._pos = [0,0];
|
||
|
newlevel._angle = 0;
|
||
|
newlevel.color = Color.green;
|
||
|
/* newlevel.toString = function() {
|
||
|
return (newlevel.unique ? "#" : "") + newlevel.file;
|
||
|
};
|
||
|
*/
|
||
|
newlevel.filejson = newlevel.save();
|
||
|
return newlevel;
|
||
|
},
|
||
|
|
||
|
addfile(file) {
|
||
|
/* TODO: Register this as a prefab for caching */
|
||
|
var lvl = this.loadfile(file);
|
||
|
this.objects.push(lvl);
|
||
|
lvl.level = this;
|
||
|
return lvl;
|
||
|
},
|
||
|
|
||
|
check_dirty() {
|
||
|
this.dirty = this.save() !== this.filejson;
|
||
|
},
|
||
|
|
||
|
start() {
|
||
|
this.objects.forEach(function(x) { if ('start' in x) x.start(); });
|
||
|
},
|
||
|
|
||
|
loadlevel(file) {
|
||
|
var lvl = Level.loadfile(file);
|
||
|
if (lvl && Game.playing())
|
||
|
lvl.start();
|
||
|
|
||
|
return lvl;
|
||
|
},
|
||
|
|
||
|
loadfile(file) {
|
||
|
if (!file.endsWith(".lvl")) file = file + ".lvl";
|
||
|
var newlevel = Level.create();
|
||
|
|
||
|
if (IO.exists(file)) {
|
||
|
newlevel.filejson = IO.slurp(file);
|
||
|
|
||
|
try {
|
||
|
newlevel.filelvl = JSON.parse(newlevel.filejson);
|
||
|
newlevel.load(newlevel.filelvl);
|
||
|
} catch (e) {
|
||
|
newlevel.ed_gizmo = function() { GUI.text("Invalid level file: " + newlevel.file, world2screen(newlevel.pos), 1, Color.red); };
|
||
|
newlevel.selectable = false;
|
||
|
throw e;
|
||
|
}
|
||
|
newlevel.file = file;
|
||
|
newlevel.dirty = false;
|
||
|
}
|
||
|
|
||
|
var scriptfile = file.replace('.lvl', '.js');
|
||
|
if (IO.exists(scriptfile)) {
|
||
|
newlevel.script = IO.slurp(scriptfile);
|
||
|
newlevel.scriptfile = scriptfile;
|
||
|
}
|
||
|
|
||
|
newlevel.from = scriptfile.replace('.js','');
|
||
|
newlevel.file = newlevel.from;
|
||
|
newlevel.run();
|
||
|
|
||
|
return newlevel;
|
||
|
},
|
||
|
|
||
|
/* Spawns all objects specified in the lvl json object */
|
||
|
load(lvl) {
|
||
|
this.clear();
|
||
|
this.levels.push_unique(this);
|
||
|
|
||
|
if (!lvl) {
|
||
|
Log.warn("Level is " + lvl + ". Need a better formed one.");
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var opos = this.pos;
|
||
|
var oangle = this.angle;
|
||
|
this.pos = [0,0];
|
||
|
this.angle = 0;
|
||
|
|
||
|
var objs;
|
||
|
var created = [];
|
||
|
|
||
|
if (typeof lvl === 'string')
|
||
|
objs = JSON.parse(lvl);
|
||
|
else
|
||
|
objs = lvl;
|
||
|
|
||
|
if (typeof objs === 'object')
|
||
|
objs = objs.array();
|
||
|
|
||
|
objs.forEach(x => {
|
||
|
if (x.from === 'group') {
|
||
|
var loadedlevel = Level.loadfile(x.file);
|
||
|
if (!loadedlevel) {
|
||
|
Log.error("Error loading level: file " + x.file + " not found.");
|
||
|
return;
|
||
|
}
|
||
|
if (!IO.exists(x.file)) {
|
||
|
loadedlevel.ed_gizmo = function() { GUI.text("MISSING LEVEL " + x.file, world2screen(loadedlevel.pos) ,1, Color.red) };
|
||
|
}
|
||
|
var objs = x.objects;
|
||
|
delete x.objects;
|
||
|
Object.assign(loadedlevel, x);
|
||
|
|
||
|
if (objs) {
|
||
|
objs.array().forEach(function(x, i) {
|
||
|
if (x.pos)
|
||
|
loadedlevel.objects[i].pos = x.pos.add(loadedlevel.pos);
|
||
|
|
||
|
if (x.angle)
|
||
|
loadedlevel.objects[i].angle = x.angle + loadedlevel.angle;
|
||
|
});
|
||
|
|
||
|
loadedlevel.unique = true;
|
||
|
}
|
||
|
loadedlevel.level = this;
|
||
|
loadedlevel.sync();
|
||
|
created.push(loadedlevel);
|
||
|
this.objects.push(loadedlevel);
|
||
|
return;
|
||
|
}
|
||
|
var prototype = gameobjects[x.from];
|
||
|
if (!prototype) {
|
||
|
Log.error(`Prototype for ${x.from} does not exist.`);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var newobj = this.spawn(gameobjects[x.from]);
|
||
|
|
||
|
delete x.from;
|
||
|
|
||
|
dainty_assign(newobj, x);
|
||
|
|
||
|
if (x._pos)
|
||
|
newobj.pos = x._pos;
|
||
|
|
||
|
|
||
|
if (x._angle)
|
||
|
newobj.angle = x._angle;
|
||
|
for (var key in newobj.components)
|
||
|
if ('sync' in newobj.components[key]) newobj.components[key].sync();
|
||
|
|
||
|
newobj.sync();
|
||
|
|
||
|
created.push(newobj);
|
||
|
});
|
||
|
|
||
|
created.forEach(function(x) {
|
||
|
if (x.varname)
|
||
|
this[x.varname] = x;
|
||
|
},this);
|
||
|
|
||
|
this.pos = opos;
|
||
|
this.angle = oangle;
|
||
|
|
||
|
return created;
|
||
|
},
|
||
|
|
||
|
clear() {
|
||
|
for (var i = this.objects.length-1; i >= 0; i--)
|
||
|
if (this.objects[i].alive)
|
||
|
this.objects[i].kill();
|
||
|
|
||
|
this.levels.remove(this);
|
||
|
},
|
||
|
|
||
|
clear_all() {
|
||
|
this.levels.forEach(function(x) { x.kill(); });
|
||
|
},
|
||
|
|
||
|
kill() {
|
||
|
if (this.level)
|
||
|
this.level.unregister(this);
|
||
|
|
||
|
Register.unregister_obj(this);
|
||
|
|
||
|
this.clear();
|
||
|
},
|
||
|
|
||
|
unregister(obj) {
|
||
|
var removed = this.objects.remove(obj);
|
||
|
|
||
|
if (removed && obj.varname)
|
||
|
delete this[obj.varname];
|
||
|
},
|
||
|
|
||
|
get pos() { return this._pos; },
|
||
|
set pos(x) {
|
||
|
var diff = x.sub(this._pos);
|
||
|
this.objects.forEach(function(x) { x.pos = x.pos.add(diff); });
|
||
|
this._pos = x;
|
||
|
},
|
||
|
|
||
|
get angle() { return this._angle; },
|
||
|
set angle(x) {
|
||
|
var diff = x - this._angle;
|
||
|
this.objects.forEach(function(x) {
|
||
|
x.angle = x.angle + diff;
|
||
|
var pos = x.pos.sub(this.pos);
|
||
|
var r = Vector.length(pos);
|
||
|
var p = Math.rad2deg(Math.atan2(pos.y, pos.x));
|
||
|
p += diff;
|
||
|
p = Math.deg2rad(p);
|
||
|
x.pos = this.pos.add([r*Math.cos(p), r*Math.sin(p)]);
|
||
|
},this);
|
||
|
this._angle = x;
|
||
|
},
|
||
|
|
||
|
flipdirty: false,
|
||
|
|
||
|
sync() {
|
||
|
this.flipx = this.flipx;
|
||
|
this.flipy = this.flipy;
|
||
|
},
|
||
|
|
||
|
_flipx: false,
|
||
|
get flipx() { return this._flipx; },
|
||
|
set flipx(x) {
|
||
|
if (this._flipx === x && (!x || !this.flipdirty)) return;
|
||
|
this._flipx = x;
|
||
|
|
||
|
this.objects.forEach(function(obj) {
|
||
|
obj.flipx = !obj.flipx;
|
||
|
var rp = obj.relpos;
|
||
|
obj.pos = [-rp.x, rp.y].add(this.pos);
|
||
|
obj.angle = -obj.angle;
|
||
|
},this);
|
||
|
},
|
||
|
|
||
|
_flipy: false,
|
||
|
get flipy() { return this._flipy; },
|
||
|
set flipy(x) {
|
||
|
if (this._flipy === x && (!x || !this.flipdirty)) return;
|
||
|
this._flipy = x;
|
||
|
|
||
|
this.objects.forEach(function(obj) {
|
||
|
var rp = obj.relpos;
|
||
|
obj.pos = [rp.x, -rp.y].add(this.pos);
|
||
|
obj.angle = -obj.angle;
|
||
|
},this);
|
||
|
},
|
||
|
|
||
|
_scale: 1.0,
|
||
|
get scale() { return this._scale; },
|
||
|
set scale(x) {
|
||
|
var diff = (x - this._scale) + 1;
|
||
|
this._scale = x;
|
||
|
|
||
|
this.objects.forEach(function(obj) {
|
||
|
obj.scale *= diff;
|
||
|
obj.relpos = obj.relpos.scale(diff);
|
||
|
}, this);
|
||
|
},
|
||
|
|
||
|
get up() {
|
||
|
return [0,1].rotate(Math.deg2rad(this.angle));
|
||
|
},
|
||
|
|
||
|
get down() {
|
||
|
return [0,-1].rotate(Math.deg2rad(this.angle));
|
||
|
},
|
||
|
|
||
|
get right() {
|
||
|
return [1,0].rotate(Math.deg2rad(this.angle));
|
||
|
},
|
||
|
|
||
|
get left() {
|
||
|
return [-1,0].rotate(Math.deg2rad(this.angle));
|
||
|
},
|
||
|
};
|