Editor mouse works; remove level.js

This commit is contained in:
John Alanbrook 2023-09-11 22:09:21 +00:00
parent 6b73624c12
commit b9316dbbe6
9 changed files with 313 additions and 816 deletions

View file

@ -34,7 +34,7 @@ struct gameobject *id2go(int id) {
}
int body2id(cpBody *body) {
return *(int*)cpBodyGetUserData(body);
return (int)cpBodyGetUserData(body);
}
cpBody *id2body(int id) {

View file

@ -98,10 +98,11 @@ char *mb2str(int btn)
void input_mouse(int btn, int state)
{
JSValue argv[2];
argv[0] = input2js(mb2str(btn));
argv[1] = jsinputstate[btn];
script_callee(pawn_callee, 2, argv);
JSValue argv[3];
argv[0] = JS_NewString(js, "emacs");
argv[1] = input2js(mb2str(btn));
argv[2] = jsinputstate[state];
script_callee(pawn_callee, 3, argv);
}
void input_mouse_move(float x, float y, float dx, float dy)

View file

@ -413,9 +413,6 @@ void openglRender(struct window *window) {
sg_end_pass();
sg_begin_default_pass(&pass_action, gif_w, gif_h);
sg_apply_pipeline(
sg_begin_default_pass(&pass_action, window->width, window->height);
sg_apply_pipeline(crt_post.pipe);
sg_apply_bindings(&crt_post.bind);

View file

@ -152,10 +152,14 @@ int frame_fps() {
return 1.0/sapp_frame_duration();
}
static double low_fps = 1/24.0; /* Chosen because of apple's 24 hz mode */
static double low_fps_c = 0.0;
void c_frame()
{
double elapsed = sapp_frame_duration();
appTime += elapsed;
low_fps_c += elapsed;
input_poll(0);
@ -181,9 +185,10 @@ void c_frame()
sim_pause();
render_dirty = 1;
}
low_fps_c = 0.0f;
}
if (sim_play == SIM_PLAY || render_dirty) {
if (sim_play == SIM_PLAY || render_dirty || low_fps_c >= low_fps) {
prof_start(&prof_draw);
window_render(&mainwin);
prof(&prof_draw);

View file

@ -27,15 +27,15 @@ var configs = {
var editor = {
dbgdraw: false,
selected: null,
selected: undefined,
selectlist: [],
moveoffset: [0,0],
startrot: 0,
rotoffset: 0,
camera: editor_camera,
edit_level: {}, /* The current level that is being edited */
working_layer: -1,
cursor: null,
working_layer: 0,
cursor: undefined,
edit_mode: "basic",
try_select() { /* nullify true if it should set selected to null if it doesn't find an object */
@ -45,10 +45,12 @@ var editor = {
/* Tries to select id */
do_select(go) {
var obj = go >= 0 ? Game.object(go) : null;
if (!obj || !obj.selectable) return null;
var obj = go >= 0 ? Game.object(go) : undefined;
if (obj)
Log.warn(obj);
if (!obj || !obj.selectable) return undefined;
if (this.working_layer > -1 && obj.draw_layer !== this.working_layer) return null;
if (this.working_layer > -1 && obj.draw_layer !== this.working_layer) return undefined;
if (obj.level !== this.edit_level) {
var testlevel = obj.level;
@ -62,7 +64,7 @@ var editor = {
return obj;
},
curpanel: null,
curpanel: undefined,
check_level_nested() {
if (this.edit_level.level) {
@ -99,7 +101,7 @@ var editor = {
protos[this.selectlist[0].__proto__.name] = pobj;
Log.warn(JSON.stringify(protos));
slurpwrite(JSON.stringify(protos, null, 2), "proto.json");
slurpwrite(JSON.stringify(protos, undefined, 2), "proto.json");
/* Save object changes to parent */
dainty_assign(this.selectlist[0].__proto__, tobj);
@ -201,71 +203,6 @@ var editor = {
wh[1] = Math.abs(end[1] - start[1]);
return {c: c, wh: wh};
},
input_lmouse_released() {
Mouse.normal();
if (!this.sel_start) return;
if (this.sel_comp) {
this.sel_start = null;
return;
}
var selects = [];
/* TODO: selects somehow gets undefined objects in here */
if (screen2world(Mouse.pos).equal(this.sel_start)) {
var sel = this.try_select();
if (sel) selects.push(sel);
} else {
var box = this.points2cwh(this.sel_start, screen2world(Mouse.pos));
box.pos = box.c;
var hits = physics.box_query(box);
hits.forEach(function(x, i) {
var obj = this.do_select(x);
if (obj)
selects.push(obj);
},this);
var levels = this.edit_level.objects.filter(function(x) { return x.file; });
var lvlpos = [];
levels.forEach(function(x) { lvlpos.push(x.pos); });
var lvlhits = physics.box_point_query(box, lvlpos);
lvlhits.forEach(function(x) { selects.push(levels[x]); });
}
this.sel_start = null;
selects = selects.flat();
selects = selects.unique();
if (selects.empty) return;
if (Keys.shift()) {
selects.forEach(function(x) {
this.selectlist.push_unique(x);
}, this);
return;
}
if (Keys.ctrl()) {
selects.forEach(function(x) {
this.selectlist.remove(x);
}, this);
return;
}
editor.selectlist = [];
selects.forEach(function(x) {
if (x !== null)
this.selectlist.push(x);
}, this);
},
mover(amt, snap) {
return function(go) { go.pos = go.pos.add(amt)};
@ -350,13 +287,13 @@ var editor = {
unselect() {
editor.selectlist = [];
this.grabselect = null;
this.sel_comp = null;
this.grabselect = undefined;
this.sel_comp = undefined;
},
sel_comp: null,
sel_comp: undefined,
comp_info: false,
brush_obj: null,
brush_obj: undefined,
camera_recalls: {},
camera_recall_stack: [],
@ -407,119 +344,17 @@ var editor = {
this.camera.zoom = zoom*1.3;
},
input_rmouse_down() {
if (Keys.ctrl() && Keys.alt())
this.camera.zoom = this.z_start * (1 + (Mouse.pos[1] - this.mousejoy[1])/500);
},
z_start: 1,
input_rmouse_released() {
Mouse.normal();
},
input_rmouse_pressed() {
if (Keys.shift()) {
this.cursor = null;
return;
}
if (Keys.ctrl() && Keys.alt()) {
this.mousejoy = Mouse.pos;
this.z_start = this.camera.zoom;
Mouse.disabled();
return;
}
if (this.brush_obj)
this.brush_obj = null;
if (this.sel_comp) {
this.sel_comp = null;
return;
}
this.unselect();
},
grabselect: null,
grabselect: undefined,
mousejoy: [0,0],
joystart: [0,0],
input_mmouse_pressed() {
if (Keys.ctrl() && Keys.alt()) {
this.mousejoy = Mouse.pos;
this.joystart = this.camera.pos;
return;
}
if (Keys.shift() && Keys.ctrl()) {
this.cursor = find_com(this.selectlist);
return;
}
if (this.brush_obj) {
this.selectlist = this.dup_objects([this.brush_obj]);
this.selectlist[0].pos = screen2world(Mouse.pos);
this.grabselect = this.selectlist[0];
return;
}
if (this.sel_comp && 'pick' in this.sel_comp) {
this.grabselect = this.sel_comp.pick(screen2world(Mouse.pos));
if (!this.grabselect) return;
this.moveoffset = this.sel_comp.gameobject.this2world(this.grabselect).sub(screen2world(Mouse.pos));
return;
}
var grabobj = this.try_select();
if (Array.isArray(grabobj)) {
this.selectlist = grabobj;
return;
}
this.grabselect = null;
if (!grabobj) return;
if (Keys.ctrl()) {
grabobj = this.dup_objects([grabobj])[0];
}
this.grabselect = grabobj;
if (!this.selectlist.includes(grabobj)) {
editor.selectlist = [];
editor.selectlist.push(grabobj);
}
this.moveoffset = this.grabselect.pos.sub(screen2world(Mouse.pos));
},
input_mmouse_released() {
Mouse.normal();
this.grabselect = null;
this.grabselect = undefined;
},
input_mmouse_down() {
if (Keys.shift() && !Keys.ctrl()) {
this.cursor = Mouse.worldpos;
return;
}
if (Keys.alt() && Keys.ctrl()) {
this.camera.pos = this.joystart.add(Mouse.pos.sub(this.mousejoy).mapc(mult, [-1,1]).scale(editor_camera.zoom));
return;
}
if (!this.grabselect) return;
if ('pos' in this.grabselect)
this.grabselect.pos = this.moveoffset.add(screen2world(Mouse.pos));
else
this.grabselect.set(this.selectlist[0].world2this(this.moveoffset.add(screen2world(Mouse.pos))));
},
stash: "",
start_play_ed() {
@ -569,7 +404,7 @@ var editor = {
var stolen = this;
if (dontsteal)
stolen = null;
stolen = undefined;
this.curpanel.open(stolen);
},
@ -584,59 +419,11 @@ var editor = {
this.curpanels = this.curpanels.filter(function(x) { return x.on; });
},
input_s_down() {
if (!this.scaleoffset) return;
var offf = this.cursor ? this.cursor : this.selected_com;
var dist = Vector.length(screen2world(Mouse.pos).sub(offf));
var scalediff = dist/this.scaleoffset;
if (this.sel_comp) {
if (!('scale' in this.sel_comp)) return;
this.sel_comp.scale = this.startscales[0] * scalediff;
return;
}
this.selectlist.forEach(function(x, i) {
x.scale = this.startscales[i] * scalediff;
if (this.cursor)
x.pos = this.cursor.add(this.startoffs[i].scale(scalediff));
}, this);
},
input_s_released() {
this.scaleoffset = null;
},
startrots: [],
startpos: [],
startoffs: [],
input_r_down() {
if (this.sel_comp && 'angle' in this.sel_comp) {
if (!('angle' in this.sel_comp)) return;
var relpos = screen2world(Mouse.pos).sub(this.sel_comp.gameobject.pos);
var anglediff = Math.rad2deg(Math.atan2(relpos.y, relpos.x)) - this.startoffset;
this.sel_comp.angle = this.startrot + anglediff;
return;
}
if (this.startrots.empty) return;
var offf = this.cursor ? this.cursor : this.selected_com;
var relpos = screen2world(Mouse.pos).sub(offf);
var anglediff = Math.rad2deg(Math.atan2(relpos[1], relpos[0]) - this.startoffset);
if (this.cursor) {
this.selectlist.forEach(function(x, i) {
x.angle = this.startrots[i] + anglediff;
x.pos = offf.add(this.startoffs[i].rotate(Math.deg2rad(anglediff)));
}, this);
} else {
this.selectlist.forEach(function(x,i) {
x.angle = this.startrots[i]+anglediff;
}, this);
}
},
snapshots: [],
curlvl: {}, /* What the level currently looks like on file */
@ -777,7 +564,7 @@ var editor = {
this.edit_level = Level.create();
},
_sel_comp: null,
_sel_comp: undefined,
get sel_comp() { return this._sel_comp; },
set sel_comp(x) {
if (this._sel_comp)
@ -830,7 +617,7 @@ var editor = {
var ypos = 200;
var lvlcolor = Color.white;
while (clvl) {
var lvlstr = clvl.file ? clvl.file : "NEW LEVEL";
var lvlstr = clvl.file ? clvl.file : "NEW ENTITY";
if (clvl.unique)
lvlstr += "#";
else if (clvl.dirty)
@ -976,10 +763,13 @@ var editor = {
lvl_history: [],
load(file) {
if (this.edit_level) this.lvl_history.push(this.edit_level.file);
this.edit_level.clear();
this.edit_level = Level.loadfile(file);
this.curlvl = this.edit_level.save();
if (this.edit_level) this.lvl_history.push(this.edit_level.ur);
this.edit_level.kill();
// this.edit_level = Level.loadfile(file);
// this.curlvl = this.edit_level.save();
this.edit_level = Primum.spawn(prototypes.get_ur(file));
Log.warn(`Loaded file ${file}`);
Log.warn(this.edit_level.pos);
this.unselect();
},
@ -1178,6 +968,32 @@ editor.inputs.r = function() {
};
editor.inputs.r.doc = "Rotate selected using the mouse while held down.";
editor.inputs.r.down = function() {
if (this.sel_comp && 'angle' in this.sel_comp) {
if (!('angle' in this.sel_comp)) return;
var relpos = screen2world(Mouse.pos).sub(this.sel_comp.gameobject.pos);
var anglediff = Math.rad2deg(Math.atan2(relpos.y, relpos.x)) - this.startoffset;
this.sel_comp.angle = this.startrot + anglediff;
return;
}
if (this.startrots.empty) return;
var offf = this.cursor ? this.cursor : this.selected_com;
var relpos = screen2world(Mouse.pos).sub(offf);
var anglediff = Math.rad2deg(Math.atan2(relpos[1], relpos[0]) - this.startoffset);
if (this.cursor) {
this.selectlist.forEach(function(x, i) {
x.angle = this.startrots[i] + anglediff;
x.pos = offf.add(this.startoffs[i].rotate(Math.deg2rad(anglediff)));
}, this);
} else {
this.selectlist.forEach(function(x,i) {
x.angle = this.startrots[i]+anglediff;
}, this);
}
};
editor.inputs['C-p'] = function() {
if (!Game.playing()) {
editor.start_play_ed();
@ -1391,6 +1207,171 @@ editor.inputs['M-j'].doc = "Remove variable names from selected objects.";
editor.inputs.lm = function() { editor.sel_start = screen2world(Mouse.pos); };
editor.inputs.lm.doc = "Selection box.";
editor.inputs.lm.released = function() {
Mouse.normal();
if (!editor.sel_start) return;
if (editor.sel_comp) {
editor.sel_start = undefined;
return;
}
var selects = [];
/* TODO: selects somehow gets undefined objects in here */
if (screen2world(Mouse.pos).equal(editor.sel_start)) {
var sel = editor.try_select();
if (sel) selects.push(sel);
} else {
var box = editor.points2cwh(editor.sel_start, screen2world(Mouse.pos));
box.pos = box.c;
var hits = physics.box_query(box);
hits.forEach(function(x, i) {
var obj = editor.do_select(x);
if (obj)
selects.push(obj);
},editor);
var levels = editor.edit_level.objects.filter(function(x) { return x.file; });
var lvlpos = [];
levels.forEach(function(x) { lvlpos.push(x.pos); });
var lvlhits = physics.box_point_query(box, lvlpos);
lvlhits.forEach(function(x) { selects.push(levels[x]); });
}
this.sel_start = undefined;
selects = selects.flat();
selects = selects.unique();
if (selects.empty) return;
if (Keys.shift()) {
selects.forEach(function(x) {
this.selectlist.push_unique(x);
}, this);
return;
}
if (Keys.ctrl()) {
selects.forEach(function(x) {
this.selectlist.remove(x);
}, this);
return;
}
editor.selectlist = [];
selects.forEach(function(x) {
if (x !== undefined)
this.selectlist.push(x);
}, this);
};
editor.inputs.rm = function() {
if (Keys.shift()) {
editor.cursor = undefined;
return;
}
if (Keys.ctrl() && Keys.alt()) {
editor.mousejoy = Mouse.pos;
editor.z_start = editor.camera.zoom;
Mouse.disabled();
return;
}
if (editor.brush_obj)
editor.brush_obj = undefined;
if (editor.sel_comp) {
editor.sel_comp = undefined;
return;
}
editor.unselect();
};
editor.inputs.mm = function() {
if (Keys.ctrl() && Keys.alt()) {
editor.mousejoy = Mouse.pos;
editor.joystart = editor.camera.pos;
return;
}
if (Keys.shift() && Keys.ctrl()) {
editor.cursor = find_com(editor.selectlist);
return;
}
if (editor.brush_obj) {
editor.selectlist = editor.dup_objects([editor.brush_obj]);
editor.selectlist[0].pos = screen2world(Mouse.pos);
editor.grabselect = editor.selectlist[0];
return;
}
if (editor.sel_comp && 'pick' in editor.sel_comp) {
editor.grabselect = editor.sel_comp.pick(screen2world(Mouse.pos));
if (!editor.grabselect) return;
editor.moveoffset = editor.sel_comp.gameobject.editor2world(editor.grabselect).sub(screen2world(Mouse.pos));
return;
}
var grabobj = editor.try_select();
if (Array.isArray(grabobj)) {
editor.selectlist = grabobj;
return;
}
editor.grabselect = undefined;
if (!grabobj) return;
if (Keys.ctrl()) {
grabobj = editor.dup_objects([grabobj])[0];
}
editor.grabselect = grabobj;
if (!editor.selectlist.includes(grabobj)) {
editor.selectlist = [];
editor.selectlist.push(grabobj);
}
editor.moveoffset = editor.grabselect.pos.sub(screen2world(Mouse.pos));
};
editor.inputs.rm.down = function() {
if (Keys.ctrl() && Keys.alt())
this.camera.zoom = this.z_start * (1 + (Mouse.pos[1] - this.mousejoy[1])/500);
};
editor.inputs.mm.down = function() {
if (Keys.shift() && !Keys.ctrl()) {
this.cursor = Mouse.worldpos;
return;
}
if (Keys.alt() && Keys.ctrl()) {
this.camera.pos = this.joystart.add(Mouse.pos.sub(this.mousejoy).mapc(mult, [-1,1]).scale(editor_camera.zoom));
return;
}
if (!this.grabselect) return;
if ('pos' in this.grabselect)
this.grabselect.pos = this.moveoffset.add(screen2world(Mouse.pos));
else
this.grabselect.set(this.selectlist[0].world2this(this.moveoffset.add(screen2world(Mouse.pos))));
};
editor.inputs.mm.released = function () { Mouse.normal(); };
editor.inputs['C-M-S-lm'] = function() { editor.selectlist[0].set_center(screen2world(Mouse.pos)); };
editor.inputs['C-M-S-lm'].doc = "Set world center to mouse position.";
@ -1418,9 +1399,7 @@ editor.inputs['C-S-g'] = function() { editor.openpanel(groupsaveaspanel); };
editor.inputs['C-S-g'].doc = "Save selected objects as a new level.";
editor.inputs.g = function() {
this.selectlist.forEach(function(x,i) {
this.moveoffsets[i] = x.pos.sub(screen2world(Mouse.pos));
}, this);
editor.selectlist.forEach(function(x,i) { editor.moveoffsets[i] = x.pos.sub(screen2world(Mouse.pos)); } );
};
editor.inputs.g.doc = "Move selected objects.";
editor.inputs.g.released = function() { editor.moveoffsets = []; };
@ -1436,7 +1415,7 @@ editor.inputs.tab = function() {
else {
var idx = sel.findIndex(this.sel_comp) + 1;
if (idx >= Object.keys(sel).length)
this.sel_comp = null;
this.sel_comp = undefined;
else
this.sel_comp = sel.nth(idx);
}
@ -1444,6 +1423,7 @@ editor.inputs.tab = function() {
editor.inputs.tab.doc = "Cycle through selected object's components.";
editor.inputs['C-g'] = function() {
if (!this.selectlist) return;
this.selectlist = this.dup_objects(this.selectlist);
editor.inputs.g();
};
@ -1497,7 +1477,7 @@ brushmode.inputs.lm.doc = "Paste selected brush.";
brushmode.inputs.b = function() {
if (editor.brush_obj) {
editor.brush_obj = null;
editor.brush_obj = undefined;
return;
}
@ -1532,6 +1512,27 @@ editor.inputs.s = function() {
};
editor.inputs.s.doc = "Scale selected.";
editor.inputs.s.down = function() {
if (!this.scaleoffset) return;
var offf = this.cursor ? this.cursor : this.selected_com;
var dist = Vector.length(screen2world(Mouse.pos).sub(offf));
var scalediff = dist/this.scaleoffset;
if (this.sel_comp) {
if (!('scale' in this.sel_comp)) return;
this.sel_comp.scale = this.startscales[0] * scalediff;
return;
}
this.selectlist.forEach(function(x, i) {
x.scale = this.startscales[i] * scalediff;
if (this.cursor)
x.pos = this.cursor.add(this.startoffs[i].scale(scalediff));
}, this);
};
editor.inputs.s.released = function() { this.scaleoffset = undefined; };
var inputpanel = {
title: "untitled",
value: "",
@ -1579,7 +1580,7 @@ var inputpanel = {
Player.players[0].uncontrol(this);
if (this.stolen) {
Player.players[0].control(this.stolen);
this.stolen = null;
this.stolen = undefined;
}
this.on = false;
@ -2003,7 +2004,7 @@ texteditor.inputs['M-n'].rep = true;
var objectexplorer = copy(inputpanel, {
title: "object explorer",
obj: null,
obj: undefined,
previous: [],
start() {
this.previous = [];
@ -2322,7 +2323,7 @@ function tab_complete(val, list) {
return check[0];
}
var ret = null;
var ret = undefined;
var i = val.length;
while (!ret && !check.empty) {

View file

@ -190,51 +190,6 @@ var Render = {
},
};
var Mouse = {
get pos() {
return cmd(45);
},
get screenpos() {
var p = this.pos;
p.y = Window.dimensions.y - p.y;
return p;
},
get worldpos() {
return screen2world(cmd(45));
},
disabled() {
cmd(46, 212995);
},
hidden() {
cmd(46, 212994);
},
normal() {
cmd(46, 212993);
},
};
var Keys = {
shift() {
return cmd(50, 340);// || cmd(50, 344);
},
ctrl() {
return cmd(50, 341);// || cmd(50, 344);
},
alt() {
return cmd(50, 342);// || cmd(50, 346);
},
super() {
return cmd(50, 343);// || cmd(50, 347);
},
};
load("scripts/physics.js");
load("scripts/input.js");
@ -259,17 +214,17 @@ var Register = {
},
kbm_input(mode, btn, state, ...args) {
if (btn === 'lmouse') btn = 'lm';
if (btn === 'rmouse') btn = 'rm';
if (btn === 'mmouse') btn = 'mm';
switch(mode) {
case "emacs":
Player.players[0].raw_input(btn, state, ...args);
break;
};
if (btn === 'lmouse')
btn = 'lm';
if (btn === 'rmouse')
btn = 'rm';
},
gamepad_playermap: [],
@ -775,6 +730,7 @@ prototypes.from_file = function(file)
prototypes.list.push(a.tag);
a.type = newobj;
a.instances = [];
newobj.ur = a;
return a;
}

View file

@ -334,7 +334,8 @@ var gameobject = {
delete props[key];
var edited = !props.empty;
return (edited ? "#" : "") + obj.name + " object " + obj.body + ", layer " + obj.draw_layer + ", phys " + obj.layer;
// return (edited ? "#" : "") + obj.name + " object " + obj.body + ", layer " + obj.draw_layer + ", phys " + obj.layer;
return obj.ur.tag;
};
obj.fullpath = function() {
@ -349,6 +350,7 @@ var gameobject = {
this.elasticity) );
obj.sync();
obj.defn('components', {});
Game.register_obj(obj);
var objects = [];
obj.objects = objects;

View file

@ -1,9 +1,54 @@
var Input = {
setgame() { cmd(77); },
setnuke() { cmd(78); },
};
var Mouse = {
get pos() {
return cmd(45);
},
get screenpos() {
var p = this.pos;
p.y = Window.dimensions.y - p.y;
return p;
},
get worldpos() {
return screen2world(cmd(45));
},
disabled() {
cmd(46, 1);
},
hidden() {
cmd(46, 1);
},
normal() {
cmd(46, 0);
},
};
var Keys = {
shift() {
return cmd(50, 340);// || cmd(50, 344);
},
ctrl() {
return cmd(50, 341);// || cmd(50, 344);
},
alt() {
return cmd(50, 342);// || cmd(50, 346);
},
super() {
return cmd(50, 343);// || cmd(50, 347);
},
};
Input.state2str = function(state) {
if (typeof state === 'string') return state;
switch (state) {

View file

@ -1,510 +0,0 @@
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;
},
add_child(obj) {
obj.unparent();
this.objects.push(obj);
obj.level = this;
},
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];
},
remove_child(child) {
this.objects.remove(child);
},
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));
},
};