diff --git a/Makefile b/Makefile index 9b9c7dc..3195722 100755 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ tools/libcdb.a: mv $(CDB)/libcdb.a tools -DOCOS = Sound gameobject Game Window physics Profile Time Player Mouse IO Log ColorMap sprite SpriteAnim Render +DOCOS = Sound gameobject Game Window physics Profile Time Player Mouse IO Log ColorMap sprite SpriteAnim Render Geometry DOCHTML := $(addsuffix .api.html, $(DOCOS)) DOCMD := $(addsuffix .api.md, $(DOCOS)) diff --git a/docs/scripting.md b/docs/scripting.md index c2eec9a..c88ab95 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -5,7 +5,8 @@ Script hooks exist to allow to modification of the game. |config.js|called before any game play, including play from editor| |game.js|called to start the game| |editorconfig.js|called when the editor is loaded, used to personalize| -|debug.js|called when play in editor is selected| +|predbg.js|called when play in editor is selected, before level load| +|debug.js|called when play in editor is selected, after level load| |dbgret.js|called when play in editor returns to editor| All objects in the Yugine can have an associated script. This script can perform setup, teardown, and handles responses for the object. @@ -15,4 +16,7 @@ All objects in the Yugine can have an associated script. This script can perform |start|called when the object is loaded| |update(dt)|called once per game frame tick| |physupdate(dt)|called once per physics tick| -|stop|called when the object is killed| \ No newline at end of file +|stop|called when the object is killed| +|debug|use draw functions with the object's world position| +|gui|draw functions in screen space| +|draw|draw functions in world space| \ No newline at end of file diff --git a/scripts/components.js b/scripts/components.js index ed51d28..3c4d41b 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -1,3 +1,16 @@ +function make_point_obj(o, p) +{ + return { + pos: p, + move(d) { + d = o.gameobject.dir_world2this(d); + p.x += d.x; + p.y += d.y; + }, + sync: o.sync.bind(o) + } +} + function assign_impl(obj, impl) { var tmp = {}; @@ -355,10 +368,6 @@ component.char2d.doc = { add_anim: "Add an animation object with the given name." }; -// sprite.timer = timer.make(sprite.advance.bind(sprite),1); -// sprite.timer.loop = true; - - /* Returns points specifying this geometry, with ccw */ var Geometry = { box(w, h) { @@ -373,9 +382,42 @@ var Geometry = { ]; return points; - } + }, + + arc(radius, angle, n, start) { + start ??= 0; + start = Math.deg2rad(start); + if (angle >= 360) + angle = 360; + + if (n <= 1) return []; + var points = []; + + angle = Math.deg2rad(angle); + var arclen = angle/n; + for (var i = 0; i < n; i++) + points.push(Vector.rotate([radius,0], start + (arclen*i))); + + return points; + }, + + circle(radius, n) { + if (n <= 1) return []; + return Geometry.arc(radius, 360, n); + }, + + ngon(radius, n) { + return Geometry.arc(radius,360,n); + }, }; +Geometry.doc = { + doc: "Functions for creating a list of points for various geometric shapes.", + box: "Create a box.", + arc: "Create an arc, made of n points.", + circle: "Create a circle, made of n points." +}; + /* For all colliders, "shape" is a pointer to a phys2d_shape, "id" is a pointer to the shape data */ var collider2d = Object.copy(component, { name: "collider 2d", @@ -416,6 +458,10 @@ component.polygon2d = Object.copy(collider2d, { hides: ['id', 'shape', 'gameobject'], _enghook: make_poly2d, points:[], + setpoints(points) { + this.points = points; + this.sync(); + }, /* EDITOR */ get spoints() { @@ -454,16 +500,8 @@ component.polygon2d = Object.copy(collider2d, { this.points = deep_copy(this.__proto__.points); var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.points); - if (p) { - return { - set pos(n) { - p.x = n.x; - p.y = n.y; - }, - get pos() { return p; }, - sync: this.sync.bind(this) - } - } + if (p) + return make_point_obj(this, p); return undefined; }, @@ -560,6 +598,11 @@ component.edge2d = Object.copy(collider2d, { return spoints; }, + setpoints(points) { + this.cpoints = points; + this.sync(); + }, + post() { this.cpoints = []; }, @@ -575,7 +618,7 @@ component.edge2d = Object.copy(collider2d, { return spoints; if (spoints.length < 2) return []; - if (this.samples === spoints.length) { + if (this.samples === 1) { if (this.looped) return spoints.wrapped(1); return spoints; } @@ -585,6 +628,8 @@ component.edge2d = Object.copy(collider2d, { knots = spoints.length + order assert knots%order != 0 */ + + n = this.samples * this.sample_calc(); if (this.looped) return Spline.sample(degrees, this.dimensions, Spline.type.open, spoints.wrapped(this.degrees), n); @@ -618,26 +663,28 @@ component.edge2d = Object.copy(collider2d, { pick(pos) { var p = Gizmos.pick_gameobject_points(pos, this.gameobject, this.cpoints); - if (p) return { - set pos(n) { p.x = n.x; p.y = n.y; }, - get pos() { return p; }, - sync: this.sync.bind(this), - }; - + if (p) + return make_point_obj(this, p); + return undefined; }, pick_all() { var picks = []; this.cpoints.forEach(function(x) { - picks.push({ - set pos(n) { x.x = n.x; x.y = n.y; }, - get pos() { return x; }, - sync: this.sync.bind(this), - }); + picks.push(make_point_obj(this,x)); }, this); return picks; }, + + sample_calc() { + return (this.spoints().length-1); + }, + + samples_per_cp() { + var s = this.sample_calc(); + return this.samples/s; + }, }); component.edge2d.impl = Object.mix({ @@ -646,7 +693,6 @@ component.edge2d.impl = Object.mix({ }, get thickness() { return cmd(112,this.id); }, sync() { - if (this.samples < this.spoints().length) this.samples = this.spoints().length; var sensor = this.sensor; var points = this.sample(this.samples); cmd_edge2d(0,this.id,points); @@ -684,11 +730,16 @@ bucket.inputs['M-b'] = function() { this.thickness++; }; bucket.inputs['M-b'].doc = "Increase spline thickness."; bucket.inputs['M-b'].rep = true; -bucket.inputs.plus = function() { this.samples++; }; +bucket.inputs.plus = function() { + this.samples++; +}; bucket.inputs.plus.doc = "Increase the number of samples of this spline."; bucket.inputs.plus.rep = true; -bucket.inputs.minus = function() { if (this.samples > this.spoints().length) this.samples--;}; +bucket.inputs.minus = function() { + if (this.samples === 1) return; + this.samples--; +}; bucket.inputs.minus.doc = "Decrease the number of samples on this spline."; bucket.inputs.minus.rep = true; @@ -698,25 +749,23 @@ bucket.inputs['C-r'].doc = "Reverse the order of the spline's points."; bucket.inputs['C-l'] = function() { this.looped = !this.looped}; bucket.inputs['C-l'].doc = "Toggle spline being looped."; -bucket.inputs['C-c'] = function() { this.type = Spline.type.clamped; }; +bucket.inputs['C-c'] = function() { this.type = Spline.type.clamped; this.looped = false; }; bucket.inputs['C-c'].doc = "Set type of spline to clamped."; -bucket.inputs['C-o'] = function() { this.type = Spline.type.open; }; +bucket.inputs['C-o'] = function() { this.type = Spline.type.open; this.looped = false; }; bucket.inputs['C-o'].doc = "Set spline to open."; -bucket.inputs['C-b'] = function() { this.type = Spline.type.bezier; }; +bucket.inputs['C-b'] = function() { this.type = Spline.type.bezier; this.looped = false;}; bucket.inputs['C-b'].doc = "Set spline to bezier."; bucket.inputs['C-M-lm'] = function() { - var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(this.gameobject.world2this,this.gameobject), 25); + var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(p => this.gameobject.this2world(p)), 25); if (idx === -1) return; this.cpoints = this.cpoints.newfirst(idx); }; bucket.inputs['C-M-lm'].doc = "Select the given point as the '0' of this spline."; -bucket.inputs.lm = function(){}; - bucket.inputs['C-lm'] = function() { var idx = 0; @@ -732,15 +781,14 @@ bucket.inputs['C-lm'] = function() { }; bucket.inputs['C-lm'].doc = "Add a point to the spline at the mouse position."; -bucket.inputs['C-M-lm'] = function() { -// var idx = grab_from_points(screen2world(Mouse.pos), this.cpoints.map(function(x) {return x.sub(this.gameobject.worldpos()); }, this), 25); - var idx = cmd(59, Mouse.worldpos.sub(this.gameobject.pos), this.cpoints, 250); +bucket.inputs['S-lm'] = function() { + var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(p => this.gameobject.this2world(p)), 25); - if (idx <= 0 || idx > this.cpoints.length) return; + if (idx < 0 || idx > this.cpoints.length) return; - this.cpoints.splice(idx-1, 1); + this.cpoints.splice(idx, 1); }; -bucket.inputs['C-M-lm'].doc = "Remove point from the spline."; +bucket.inputs['S-lm'].doc = "Remove point from the spline."; bucket.inputs.lb = function() { var np = []; diff --git a/scripts/editor.js b/scripts/editor.js index 4b36b68..05eeaa7 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -210,6 +210,7 @@ var editor = { Player.players[0].uncontrol(this); Player.players[0].control(limited_editor); Register.unregister_obj(this); + load("predbg.js"); editor.dbg_play = Primum.spawn(this.dbg_ur); editor.dbg_play.pos = [0,0]; load("debug.js"); @@ -596,7 +597,7 @@ var editor = { var ur = prototypes.get_ur(file); if (!ur) return; var obj = editor.edit_level.spawn(ur); - obj.pos = Mouse.worldpos; + obj.set_worldpos(Mouse.worldpos); this.selectlist = [obj]; }, @@ -904,6 +905,8 @@ editor.inputs['C-s'] = function() { Object.values(saveobj.objects).forEach(function(x) { x._ed.check_dirty(); }); Game.objects.forEach(function(x) { + if (typeof x !== 'object') return; + if (!('_ed' in x)) return; if (x._ed.dirty) return; x.revert(); x._ed.check_dirty(); @@ -1153,11 +1156,11 @@ editor.inputs.mouse.move = function(pos, dpos) if (editor.z_start) editor.camera.zoom -= dpos.y/500; else if (editor.joystart) - editor.camera.pos = editor.camera.pos.sub(dpos.scale(editor.camera.zoom)); + editor.camera.pos = editor.camera.pos.sub(Game.camera.dir_view2world(dpos)); } editor.grabselect?.forEach(function(x) { - x.pos = x.pos.add(dpos.scale(editor.camera.zoom)); + x.move(Game.camera.dir_view2world(dpos)); if ('sync' in x) x.sync(); }); @@ -1184,12 +1187,8 @@ editor.inputs.mouse.move = function(pos, dpos) editor.inputs.mouse.scroll = function(scroll) { scroll.y *= -1; - editor.grabselect?.forEach(function(x) { - x.pos = x.pos.add(scroll.scale(editor.camera.zoom)); - }); - - scroll.y *= -1; - editor.camera.pos = editor.camera.pos.sub(scroll.scale(editor.camera.zoom * 3).scale([1,-1])); +// editor.grabselect?.forEach(x => x.move(Game.camera.dir_view2world(scroll))); + editor.camera.move(Game.camera.dir_view2world(scroll.scale(-3))); } editor.inputs.mouse['C-scroll'] = function(scroll) @@ -2000,7 +1999,7 @@ var entitylistpanel = Object.copy(inputpanel, { this.level = editor.edit_level; }, - guibody() { +/* guibody() { Nuke.newline(4); Nuke.label("Object"); Nuke.label("Visible"); @@ -2018,6 +2017,7 @@ var entitylistpanel = Object.copy(inputpanel, { if (editor.selectlist.includes(x)) Nuke.label("T"); else Nuke.label("F"); }); }, +*/ }); var limited_editor = {}; diff --git a/scripts/engine.js b/scripts/engine.js index 163b962..5a79672 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -272,7 +272,7 @@ var Device = { load("scripts/gui.js"); -var timer = { +var ctimer = { make(fn, secs,obj,loop,app) { obj ??= globalThis; app ??= false; @@ -308,6 +308,61 @@ var timer = { get time() { return cmd(29, this.id); }, get pct() { return this.remain / this.time; }, }; + +var timer = { + time: 1, + remain: 1, + loop: false, + on: false, + start() { + this.on = true; + }, + + restart() { + this.remain = this.time; + this.start(); + }, + + update(dt) { + if (!this.on) return; + + this.remain -= dt; + if (this.remain <= 0) + this.fire(); + }, + + fire() { + this.fn(); + if (this.loop) + this.restart(); + }, + + pct() { return this.remain / this.time; }, + + kill() { + Register.unregister_obj(this); + }, + + delay(fn, secs, desc) { + var t = timer.make(fn,secs,desc); + t.loop = false; + t.restart(); + t.fn = function() { fn(); t.kill(); }; + return function() { t.kill(); }; + }, + oneshot(fn, secs, obj, desc) { + this.delay(fn,secs,desc); + }, + make(fn, secs, desc) { + var t = Object.create(this); + Object.assign(t, desc); + t.time = secs; + t.remain = secs; + t.fn = fn; + Register.update.register(t.update, t); + return t; + }, +}; timer.toJSON = function() { return undefined; }; timer.doc = { doc: "Quickly make timers to fire off events once or multiple times.", diff --git a/scripts/entity.js b/scripts/entity.js index da0b1ba..d10ad77 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -44,7 +44,7 @@ var gameobject = { timers:[], delay(fn, seconds) { - var t = timer.oneshot(fn, seconds, this, false); + var t = timer.oneshot(fn.bind(this), seconds, this, false); this.timers.push(t); return function() { t.kill(); }; }, @@ -223,6 +223,9 @@ var gameobject = { torque(val) { cmd(153, this.body, val); }, world2this(pos) { return cmd(70, this.body, pos); }, this2world(pos) { return cmd(71, this.body,pos); }, + dir_world2this(dir) { return cmd(160, this.body, dir); }, + dir_this2world(dir) { return cmd(161, this.body, dir); }, + set layer(x) { cmd(75,this.body,x); }, get layer() { cmd(77,this.body); }, alive() { return this.body >= 0; }, @@ -258,9 +261,13 @@ 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()]; delete jobj.objects; Object.keys(jobj).forEach(function(x) { - this[x] = this.__proto__[x]; + if (lobj && x in lobj) + this[x] = lobj[x]; + else + this[x] = this.__proto__[x]; }, this); this.sync(); }, @@ -301,7 +308,7 @@ var gameobject = { phys:Physics.static, flipx() { return this.scale.x < 0; }, flipy() { return this.scale.y < 0; }, - scale:[1,1,1], + scale:[1,1], mirror(plane) { this.scale = Vector.reflect(this.scale, plane); }, @@ -834,13 +841,18 @@ prototypes.from_obj("camera2d", { z *= x; cmd(62, z); }, + + realzoom() { + return cmd(135); + }, speedmult: 1.0, selectable: false, - world2this(pos) { return cmd(70, this.body, pos); }, - this2world(pos) { return cmd(71, this.body,pos); }, + dir_view2world(dir) { + return dir.scale(this.realzoom()); + }, view2world(pos) { return cmd(137,pos); diff --git a/scripts/std.js b/scripts/std.js index f84af87..159c1cf 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -256,3 +256,8 @@ Cmdline.register_cmd("cjson", function(json) { STD.exit(0); }, "Clean up a jso file."); + +Cmdline.register_cmd("r", function(script) { + run(script); + STD.exit(0); +}, "Run a script."); diff --git a/source/engine/2dphysics.c b/source/engine/2dphysics.c index 917affc..e85b2ec 100644 --- a/source/engine/2dphysics.c +++ b/source/engine/2dphysics.c @@ -587,9 +587,9 @@ void duk_call_phys_cb(HMM_Vec2 norm, struct callee c, int hit, cpArbiter *arb) { HMM_Vec2 srfv; srfv.cp = cpArbiterGetSurfaceVelocity(arb); JS_SetPropertyStr(js, obj, "velocity", vec2js(srfv)); - srfv.cp = cpArbiterGetPointA(arb,0); - JS_SetPropertyStr(js, obj, "pos", vec2js(srfv)); - JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0))); +// srfv.cp = cpArbiterGetPointA(arb,0); +// JS_SetPropertyStr(js, obj, "pos", vec2js(srfv)); +// JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0))); JS_SetPropertyStr(js, obj, "id", JS_NewInt32(js,hit)); JS_SetPropertyStr(js,obj,"obj", JS_DupValue(js,id2go(hit)->ref)); @@ -632,10 +632,8 @@ static cpBool handle_collision(cpArbiter *arb, int type) { break; case CTYPE_SEP: - if (JS_IsObject(go->cbs.separate.obj)) { - YughWarn("Made it here; separate."); + if (JS_IsObject(go->cbs.separate.obj)) duk_call_phys_cb(norm1, go->cbs.separate, g2, arb); - } break; } diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index f8f406a..3845e2d 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -1146,6 +1146,12 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 159: ret = bool2js(js2go(argv[1])->gravity); break; + case 160: + ret = vec2js(mat_t_dir(t_world2go(js2go(argv[1])), js2vec2(argv[2]))); + break; + case 161: + ret = vec2js(mat_t_dir(t_go2world(js2go(argv[1])), js2vec2(argv[2]))); + break; } if (str)