diff --git a/doc/prosperon.org b/doc/prosperon.org index 5f6a6a8..6941303 100644 --- a/doc/prosperon.org +++ b/doc/prosperon.org @@ -119,7 +119,6 @@ When an actor dies, all of the actors that have it as their master[fn::What a mo *** Turns Actors get fragments of time called a *turn*. Actors which belong to different systems can have different lengths of turns. - *** Actor files Actor files end with the extension *.jso*[fn::"Javascript object".]. They list a series of functions to call on a newly formed actor. Actors have a number of useful functions which are called as defined. @@ -184,7 +183,11 @@ Game worlds are made of entities. Entities are a type of actor with a number of The first and most masterful entity is the Primum. The Primum has no components, and its rotation and position are zero. It defines the center of the game. #+end_scholium -In editor mode, when an entity moves, all of its *padawans* also move. When the game is actively simulating, this only holds if there are physical constraints between them. +In editor mode, when an entity moves, all of its *padawans* also move. + +When the game is actively simulating, this only holds if there are physical constraints between them. + +Prosperon automatically generates physical pin constraints between objects with the appropriate physical properties. *** Adding Components Entities can have *components*. Components are essentially javascript wrappers over C code into the engine. Scripting is done to set the components up on entities, after which most of the work is done by the C plugin. @@ -206,7 +209,7 @@ Components only work in the context of an entity. They have no meaning outside o While components can be added via scripting, it is easier to add them via the editor, as we will later see. *** Ur system -The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. Entities can be created from ur types using the *spawn* function. +The ur[fn::A German prefix meaning primitive, original, or earliest.] system is a prototypical inheritence system used by the actor files. When actor files are loaded, they are stored as an ur. An *ur* represents a set of instructions to create the (text, config) needed to spawn an actor or entity. #+begin_scholium Create an ur from the *hello* files above, and then spawn it. @@ -217,11 +220,21 @@ 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 -Each ur type has some useful fields. +Each ur has the following fields. -| field | description | -|-----------+----------------------------------| -| instances | An array of instances of this ur | +| field | description | +|-----------+-------------------------------------------------------------| +| instances | An array of instances of this ur | +| name | Name of the ur | +| text | Path to the script file | +| data | Object to write to a newly generated actor | +| proto | An object that looks like a freshly made entity from the ur | + +An *ur* has a full path given like ~ur.goblin.big~. ~goblin~ and ~big~ can both possibly have a *.jso* script as well as a *data* file. + +When ~goblin.big~ is created, the new object has the ~goblin~ script run on it, followed by the ~big~ script. The ~data~ fields consist of objects prototyped from each other, so that the ~__proto__~ of ~big.data~ is ~goblin.data~. All fields of this objects are assigned to the ~big goblin~. + +The unaltered form of every ur-based-entity is saved in the ur's ~proto~ field. As you edit objects, the differences between how your object is now, compared to its ~ur.proto~ is a list of differences. These differences can be rolled into the ~ur~, or saved as a subtype. *** Prototyping Entities Ur types are the prototype of created entities. This makes it trivial to change huge swathes of the game, or make tiny adjustments to single objects, in a natural and intuitive way. When a value is changed on an entity, it is private. When a value is changed on an ur, it propogates to all entities. Values cannot be added or removed in subtypes. diff --git a/scripts/actor.js b/scripts/actor.js new file mode 100644 index 0000000..c37fa52 --- /dev/null +++ b/scripts/actor.js @@ -0,0 +1,72 @@ +var actor = {}; +var a_db = {}; + +actor.spawn = function(script, config){ + if (typeof script !== 'string') return undefined; + if (!a_db[script]) a_db[script] = io.slurp(script); + var padawan = Object.create(actor); + eval_env(a_db[script], padawan); + + if (typeof config === 'object') + Object.merge(padawan,config); + + padawan.padawans = []; + padawan.timers = []; + padawan.master = this; + Object.hide(padawan, "master","timers", "padawans"); + this.padawans.push(padawan); + return padawan; +}; + +actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`; + +actor.timers = []; +actor.kill = function(){ + if (this.__dead__) return; + this.timers.forEach(t => t.kill()); + if (this.master) + delete this.master[this.toString()]; + this.padawans.forEach(p => p.kill()); + this.padawans = []; + this.__dead__ = true; + if (typeof this.die === 'function') this.die(); + if (typeof this.stop === 'function') this.stop(); +}; + +actor.kill.doc = `Remove this actor and all its padawans from existence.`; + +actor.delay = function(fn, seconds) { + var t = Object.create(timer); + t.remain = seconds; + t.kill = () => { + timer.kill.call(t); + delete this.timers[t.toString()]; + } + t.fire = () => { + if (this.__dead__) return; + fn(); + t.kill(); + }; + Register.appupdate.register(t.update, t); + this.timers.push(t); + return function() { t.kill(); }; +}; + +actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`; + +actor.padawans = []; + +actor.remaster = function(to){ + delete this.master.padawans[this.toString()]; + this.master = to; + to.padawans.push(this); +}; + +global.app = Object.create(actor); + +app.die = function() +{ + Game.quit(); +} + +return {actor, app}; diff --git a/scripts/ai.js b/scripts/ai.js index 79e3f8a..5c65c76 100644 --- a/scripts/ai.js +++ b/scripts/ai.js @@ -38,7 +38,7 @@ var ai = { if (Vector.length(dir) < 10) return true; this.velocity = Vector.norm(this.randomloc.sub(this.pos)).scale(20); - return false; + return False; } }, @@ -61,3 +61,5 @@ var ai = { }; }, }; + +return {ai}; diff --git a/scripts/base.js b/scripts/base.js index bdffae0..1da9d57 100644 --- a/scripts/base.js +++ b/scripts/base.js @@ -465,6 +465,18 @@ Object.containingKey = function(obj, prop) return o; } +Object.access = function(obj, name) +{ + var dig = name.split('.'); + + for (var i of dig) { + obj = obj[i]; + if (!obj) return undefined; + } + + return obj; +} + Object.isAccessor = function(obj, prop) { var o = Object.containingKey(obj,prop); @@ -490,11 +502,11 @@ Object.mergekey = function(o1,o2,k) o1[k] = o2[k]; } } else - Object.defineProperty(o1, k, Object.getOwnPropertyDescriptor(o2,k)); -// o1[k] = o2[k]; + o1[k] = o2[k]; } /* Same as merge from Ruby */ +/* Adds objs key by key to target */ Object.merge = function(target, ...objs) { for (var obj of objs) @@ -575,10 +587,12 @@ Object.defineProperty(Object.prototype, 'obscure', { Object.defineProperty(Object.prototype, 'mixin', { value: function(obj) { if (typeof obj === 'string') { - obj = use(obj); - if (!obj) return; + var script = io.slurp(obj); + obj = eval_env(script, this, obj); } - Object.assign(this, obj); + + if (obj) + Object.mixin(this, obj); }, }); @@ -771,6 +785,28 @@ Object.defineProperty(String.prototype, 'set_ext', { value: function(val) { return this.strip_ext() + val; } }); +Object.defineProperty(String.prototype, 'folder_same_name', { + value: function() { + var dirs = this.dir().split('/'); + return dirs.last() === this.name(); + } +}); + +Object.defineProperty(String.prototype, 'up_path', { + value: function() { + var base = this.base(); + var dirs = this.dir().split('/'); + dirs.pop(); + return dirs.join('/') + base; + } +}); + +Object.defineProperty(String.prototype, 'resolve', { + value: function(path) { + + }, +}); + Object.defineProperty(String.prototype, 'fromlast', { value: function(val) { var idx = this.lastIndexOf(val); @@ -816,7 +852,10 @@ Object.defineProperty(String.prototype, 'base', { }); Object.defineProperty(String.prototype, 'dir', { - value: function() { return this.tolast('/'); } + value: function() { + if (!this.includes('/')) return ""; + return this.tolast('/'); + } }); Object.defineProperty(String.prototype, 'splice', { diff --git a/scripts/camera2d.jso b/scripts/camera2d.jso new file mode 100644 index 0000000..fd47645 --- /dev/null +++ b/scripts/camera2d.jso @@ -0,0 +1,19 @@ +this.phys = physics.kinematic; +this.dir_view2world = function(dir) { return dir.scale(this.realzoom()); }; +this.view2world = function(pos) { return cmd(137,pos); }; +this.world2view = function(pos) { return cmd(136,pos); }; +this.realzoom = function() { return cmd(135); }; + +this.mixin({ + get zoom() { + var z = Game.native.y / Window.dimensions.y; + return cmd(135)/z; + }, + + set zoom(x) { + x = Math.clamp(x,0.1,10); + var z = Game.native.y / Window.dimensions.y; + z *= x; + cmd(62,z); + }, +}); \ No newline at end of file diff --git a/scripts/components.js b/scripts/components.js index 99bb5aa..43c8e92 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -42,8 +42,7 @@ var component = { extend(spec) { return Object.copy(this, spec); }, }; -component.util = {}; -component.util.make_point_obj = function(o, p) +var make_point_obj = function(o, p) { return { pos: p, @@ -56,7 +55,7 @@ component.util.make_point_obj = function(o, p) } } -component.util.assign_impl = function(obj, impl) +var assign_impl = function(obj, impl) { var tmp = {}; for (var key of Object.keys(impl)) @@ -172,7 +171,7 @@ component.sprite.impl = { var dim = this.dimensions(); dim = dim.scale(this.gameobject.gscale()); var realpos = dim.scale(0.5).add(this.pos); - return cwh2bb(realpos,dim); + return bbox.fromcwh(realpos,dim); }, kill() { cmd(9,this.id); }, @@ -801,7 +800,7 @@ component.circle2d = Object.copy(collider2d, { toString() { return "circle2d"; }, boundingbox() { - return cwh2bb(this.offset.scale(this.gameobject.scale), [this.radius,this.radius]); + return bbox.fromcwh(this.offset.scale(this.gameobject.scale), [this.radius,this.radius]); }, hides: ['gameobject', 'id', 'shape', 'scale'], diff --git a/scripts/debug.js b/scripts/debug.js index 126b603..3b25d35 100644 --- a/scripts/debug.js +++ b/scripts/debug.js @@ -1,77 +1,3 @@ -/* All draw in screen space */ -Object.assign(render, { - point(pos,size,color) { - color ??= Color.blue; - render.circle(pos,size,color); - }, - - line(points, color, thickness) { - thickness ??= 1; - color ??= Color.white; - cmd(83, points, color, thickness); - }, - - poly(points, color) { cmd_points(0,points,color); }, - - circle(pos, radius, color) { cmd(115, pos, radius, color); }, - - /* size here is arm length - size of 2 is 4 height total */ - cross(pos, size, color) { - color ??= Color.red; - var a = [ - pos.add([0,size]), - pos.add([0,-size]) - ]; - var b = [ - pos.add([size,0]), - pos.add([-size,0]) - ]; - - render.line(a,color); - render.line(b,color); - }, - - arrow(start, end, color, wingspan, wingangle) { - color ??= Color.red; - wingspan ??= 4; - wingangle ??=10; - - var dir = end.sub(start).normalized(); - var wing1 = [ - Vector.rotate(dir, wingangle).scale(wingspan).add(end), - end - ]; - var wing2 = [ - Vector.rotate(dir,-wingangle).scale(wingspan).add(end), - end - ]; - render.line([start,end],color); - render.line(wing1,color); - render.line(wing2,color); - }, - - rectangle(lowerleft, upperright, color) { - var pos = lowerleft.add(upperright).map(x=>x/2); - var wh = [upperright.x-lowerleft.x,upperright.y-lowerleft.y]; - render.box(pos,wh,color); - }, - - box(pos, wh, color) { - color ??= Color.white; - cmd(53, pos, wh, color); - }, - -}); - -render.doc = "Draw shapes in screen space."; -render.circle.doc = "Draw a circle at pos, with a given radius and color."; -render.cross.doc = "Draw a cross centered at pos, with arm length size."; -render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle."; -render.poly.doc = "Draw a concave polygon from a set of points."; -render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright."; -render.box.doc = "Draw a box centered at pos, with width and height in the tuple wh."; -render.line.doc = "Draw a line from a set of points, and a given thickness."; - var Debug = { fn_break(fn, obj) { if (typeof fn !== 'function') return; @@ -339,7 +265,7 @@ Time.doc.time = "Seconds elapsed since the game started."; Time.doc.pause = "Pause the game by setting the timescale to 0; remembers the current timescale on play."; Time.doc.play = "Resume the game after using Time.pause."; -Player.players[0].control(DebugControls); +player[0].control(DebugControls); Register.gui.register(Debug.draw, Debug); Debug.api = {}; diff --git a/scripts/diff.js b/scripts/diff.js index 410136a..ad1e729 100644 --- a/scripts/diff.js +++ b/scripts/diff.js @@ -32,6 +32,7 @@ function ediff(from,to) if (typeof v === 'function') return; if (typeof v === 'undefined') return; + if (Array.isArray(v)) { if (!Array.isArray(to[key]) || v.length !== to[key].length) { var r = ediff(v,[]); @@ -54,7 +55,8 @@ function ediff(from,to) } if (typeof v === 'number') { - if (!to || v !== to[key]) + if (!isFinite(v)) v = null; // Squash infinity to null + if (v !== to[key]) ret[key] = v; return; } @@ -67,7 +69,7 @@ function ediff(from,to) return ret; } -ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive."; +ediff.doc = "Given a from and to object, returns an object that, if applied to from, will make it the same as to. Does not include deletion; it is only additive. If one element in an array is different, the entire array is copied. Squashes infinite numbers to null for use in JSON."; function samediff(from, to) { diff --git a/scripts/editor.js b/scripts/editor.js index 927d0ee..669c7e4 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -2,15 +2,12 @@ Editor-only variables on objects selectable */ -//prototypes.generate_ur('.'); var editor = { toString() { return "editor"; }, grid_size: 100, ruler_mark_px: 100, grid_color: Color.green.alpha(0.3), - - dbg_ur: "arena.level1", machine: undefined, device_test: undefined, selectlist: [], @@ -40,10 +37,10 @@ var editor = { if (!obj) return; if (!obj._ed.selectable) return undefined; - if (obj.level !== this.edit_level) { - var testlevel = obj.level; - while (testlevel && testlevel.level !== Primum && testlevel.level !== this.edit_level && testlevel !== testlevel.level) - testlevel = testlevel.level; + if (obj.master !== this.edit_level) { + var testlevel = obj.master; + while (testlevel && testlevel.master !== world && testlevel.master !== this.edit_level && testlevel !== testlevel.master) + testlevel = testlevel.master; return testlevel; } @@ -57,7 +54,7 @@ var editor = { curpanel: undefined, check_level_nested() { - if (this.edit_level.level) { + if (this.edit_level.master) { this.openpanel(gen_notify("Can't close a nested level. Save up to the root before continuing.")); return true; } @@ -186,45 +183,45 @@ var editor = { start_play_ed() { this.stash = this.desktop.instance_obj(); - Primum.clear(); - load("config.js"); + world.clear(); + global.mixin("config.js"); Game.play(); - Player.players[0].uncontrol(this); - Player.players[0].control(limited_editor); + player[0].uncontrol(this); + player[0].control(limited_editor); editor.cbs.forEach(cb => cb()); editor.cbs = []; - load("predbg.js"); + global.mixin("predbg.js"); console.warn(`starting game with ${this.dbg_ur}`); - editor.dbg_play = Primum.spawn(this.dbg_ur); + editor.dbg_play = world.spawn(this.dbg_ur); editor.dbg_play.pos = [0,0]; - load("debug.js"); + global.mixin("debug.js"); }, start_play() { - Primum.clear(); - load("config.js"); + world.clear(); + global.mixin("config.js"); Game.play(); - Player.players[0].uncontrol(this); - Player.players[0].control(limited_editor); + player[0].uncontrol(this); + player[0].control(limited_editor); editor.cbs.forEach(cb=>cb()); editor.cbs = []; - load("game.js"); + global.mixin("game.js"); }, cbs: [], enter_editor() { Game.pause(); - Player.players[0].control(this); - Player.players[0].uncontrol(limited_editor); + player[0].control(this); + player[0].uncontrol(limited_editor); editor.cbs.push(Register.gui.register(editor.gui.bind(editor))); editor.cbs.push(Register.draw.register(editor.draw.bind(editor))); editor.cbs.push(Register.debug.register(editor.ed_debug.bind(editor))); editor.cbs.push(Register.update.register(GUI.controls.update, GUI.controls)); - this.desktop = Primum.spawn(ur.arena); - Primum.rename_obj(this.desktop.toString(), "desktop"); + this.desktop = world.spawn(); + world.rename_obj(this.desktop.toString(), "desktop"); this.edit_level = this.desktop; editor.edit_level._ed.selectable = false; if (this.stash) { @@ -232,7 +229,7 @@ var editor = { Object.dainty_assign(this.desktop, this.stash); } this.selectlist = []; - editor.camera = Primum.spawn(ur.camera2d); + editor.camera = world.spawn("scripts/camera2d.jso"); editor.camera._ed.selectable = false; Game.view_camera(editor.camera); }, @@ -244,11 +241,11 @@ var editor = { openpanel(panel) { if (this.curpanel) { this.curpanel.close(); - Player.players[0].uncontrol(this.curpanel); + player[0].uncontrol(this.curpanel); } this.curpanel = panel; - Player.players[0].control(this.curpanel); + player[0].control(this.curpanel); this.curpanel.open(); }, @@ -271,6 +268,7 @@ var editor = { }, snapshot() { + return; // TODO: Implement var dif = this.edit_level.json_obj(); if (!dif) return; @@ -338,10 +336,6 @@ var editor = { } }, - load_desktop(d) { - - }, - draw_objects_names(obj,root,depth){ if (!obj) return; if (!obj.objects) return; @@ -358,13 +352,13 @@ var editor = { get sel_comp() { return this._sel_comp; }, set sel_comp(x) { if (this._sel_comp) - Player.players[0].uncontrol(this._sel_comp); + player[0].uncontrol(this._sel_comp); this._sel_comp = x; if (this._sel_comp) { console.info("sel comp is now " + this._sel_comp); - Player.players[0].control(this._sel_comp); + player[0].control(this._sel_comp); } }, @@ -413,9 +407,9 @@ var editor = { var clvl = thiso; var lvlchain = []; - while (clvl !== Primum) { + while (clvl !== world) { lvlchain.push(clvl); - clvl = clvl.level; + clvl = clvl.master; } lvlchain.push(clvl); @@ -450,11 +444,7 @@ var editor = { GUI.text("$$$$$$", [0,ypos],1,editor.color_depths[depth]); this.selectlist.forEach(function(x) { - var sname = x.__proto__.toString(); - x.check_dirty(); - if (x._ed.dirty) sname += "*"; - - GUI.text(sname, x.screenpos().add([0, 32]), 1, Color.editor.ur); + GUI.text(x.urstr(), x.screenpos().add([0, 32]), 1, Color.editor.ur); GUI.text(x.worldpos().map(function(x) { return Math.round(x); }), x.screenpos(), 1, Color.white); render.cross(x.screenpos(), 10, Color.blue); }); @@ -532,9 +522,7 @@ var editor = { lvl_history: [], load(file) { - var ur = prototypes.get_ur(file); - if (!ur) return; - var obj = editor.edit_level.spawn(ur); + var obj = editor.edit_level.spawn(Object.access(ur, file)); obj.set_worldpos(Mouse.worldpos); this.selectlist = [obj]; }, @@ -550,10 +538,11 @@ var editor = { /* Checking to save an entity as a subtype. */ /* sub is the name of the (sub)type; obj is the object to save it as */ saveas_check(sub, obj) { + return; if (!sub) return; obj ??= editor.selectlist[0]; - var curur = prototypes.get_ur(sub); +// var curur = prototypes.get_ur(sub); if (curur) { notifypanel.action = editor.saveas; @@ -570,7 +559,7 @@ var editor = { editor.selectlist = [nobj]; return; } - editor.edit_level = editor.edit_level.level; + editor.edit_level = editor.edit_level.master; } var t = obj.transform(); @@ -633,7 +622,7 @@ editor.inputs.n = function() { var o = editor.try_select(); if (!o) return; if (o === editor.selectlist[0]) return; - if (o.level !== editor.selectlist[0].level) return; + if (o.master !== editor.selectlist[0].master) return; var tpos = editor.selectlist[0].pos; tpos.x *= -1; @@ -646,7 +635,7 @@ editor.inputs['M-n'] = function() var o = editor.try_select(); if (!o) return; if (o === editor.selectlist[0]) return; - if (o.level !== editor.selectlist[0].level) return; + if (o.master !== editor.selectlist[0].master) return; var tpos = editor.selectlist[0].pos; tpos.y *= -1; @@ -662,7 +651,7 @@ editor.inputs['h'] = function() { }; editor.inputs['h'].doc = "Toggle object hidden."; -editor.inputs['C-h'] = function() { Primum.objects.forEach(function(x) { x.visible = true; }); }; +editor.inputs['C-h'] = function() { world.objects.forEach(function(x) { x.visible = true; }); }; editor.inputs['C-h'].doc = "Unhide all objects."; editor.inputs['C-e'] = function() { editor.openpanel(assetexplorer); }; @@ -733,9 +722,9 @@ editor.inputs['C-f'] = function() { editor.inputs['C-f'].doc = "Tunnel into the selected level object to edit it."; editor.inputs['C-F'] = function() { - if (editor.edit_level.level === Primum) return; + if (editor.edit_level.master === world) return; - editor.edit_level = editor.edit_level.level; + editor.edit_level = editor.edit_level.master; editor.unselect(); editor.reset_undos(); }; @@ -807,7 +796,7 @@ editor.inputs['C-space'] = function() { editor.inputs['C-space'].doc = "Search to execute a specific command."; editor.inputs['M-m'] = function() { -// Player.players[0].control(rebinder); +// player[0].control(rebinder); }; editor.inputs['M-m'].doc = "Rebind a shortcut. Usage: M-m SHORTCUT TARGET"; @@ -838,7 +827,8 @@ editor.inputs['C-s'] = function() { var savejs = saveobj.json_obj(); Object.merge(saveobj.__proto__, savejs); if (savejs.objects) saveobj.__proto__.objects = savejs.objects; - var path = prototypes.ur_stem(saveobj.ur.toString()) + ".json"; +// var path = prototypes.ur_stem(saveobj.ur.toString()) + ".json"; + path = "CHANGETHIS"; io.slurpwrite(path, JSON.stringify(saveobj.__proto__,null,1)); console.warn(`Wrote to file ${path}`); @@ -1053,7 +1043,7 @@ editor.inputs['C-M-lm'] = function() { var go = physics.pos_query(Mouse.worldpos); if (!go) return; - editor.edit_level = go.level; + editor.edit_level = go.master; } editor.inputs['C-M-mm'] = function() { @@ -1380,7 +1370,7 @@ var inputpanel = { start() {}, close() { - Player.players[0].uncontrol(this); + player[0].uncontrol(this); this.on = false; if ('on_close' in this) this.on_close(); @@ -1505,6 +1495,7 @@ var replpanel = Object.copy(inputpanel, { this.value = ""; this.caret = 0; var ret = function() {return eval(ecode);}.call(repl_obj); + if (typeof ret === 'object') ret = json.encode(ret,null,1); console.say(ret); }, @@ -1797,7 +1788,7 @@ var openlevelpanel = Object.copy(inputpanel, { }, start() { - this.allassets = prototypes.list.sort(); + this.allassets = ur._list.sort(); this.assets = this.allassets.slice(); this.caret = 0; var click_ur = function(btn) { @@ -1954,7 +1945,7 @@ var entitylistpanel = Object.copy(inputpanel, { title: "Level object list", level: {}, start() { - this.level = editor.edit_level; + this.master = editor.edit_level; }, }); @@ -1978,9 +1969,9 @@ limited_editor.inputs['M-p'] = function() limited_editor.inputs['C-q'] = function() { - Primum.clear(); - load("editorconfig.js"); - load("dbgret.js"); + world.clear(); + global.mixin("editorconfig.js"); + global.mixin("dbgret.js"); editor.enter_editor(); } diff --git a/scripts/engine.js b/scripts/engine.js index 48f407a..4aca880 100644 --- a/scripts/engine.js +++ b/scripts/engine.js @@ -14,19 +14,28 @@ function use(file) } use.files = {}; +function include(file,that) +{ + if (!that) return; + var c = io.slurp(file); + eval_env(c, that, file); +} + function eval_env(script, env, file) { env ??= {}; file ??= "SCRIPT"; -// script = `(function() { ${script} })();`; - eval(`(function() { ${script} }).call(env);`); -// cmd(123,script,global,file); -// return eval(script); - -// return function(str) { return eval(str); }.call(env, script); + script = `(function() { ${script} }).call(this);`; + return cmd(123,script,env,file); } + eval_env.dov = `Counterpart to /load_env/, but with a string.`; +function feval_env(file, env) +{ + eval_env(io.slurp(file), env, file); +} + function load_env(file,env) { env ??= global; @@ -43,12 +52,12 @@ var load = use; Object.assign(global, use("scripts/base.js")); global.obscure('global'); -global.mixin(use("scripts/std.js")); -global.mixin(use("scripts/diff.js")); +global.mixin("scripts/std.js"); +global.mixin("scripts/diff.js"); console.level = 1; -global.mixin(use("scripts/color.js")); +global.mixin("scripts/color.js"); var prosperon = {}; prosperon.version = cmd(255); @@ -104,7 +113,7 @@ Range is given by a semantic versioning number, prefixed with nothing, a ~, or a ^ means that MAJOR must match exactly, but any MINOR and PATCH greater or equal is valid.`; -global.mixin(use("scripts/gui.js")); +global.mixin("scripts/gui.js"); var timer = { update(dt) { @@ -130,61 +139,13 @@ var timer = { }, }; -load("scripts/tween.js"); - -var render = { - normal() { cmd(67);}, - wireframe() { cmd(68); }, - pass() { }, -}; - -render.doc = { - doc: "Functions for rendering modes.", - normal: "Final render with all lighting.", - wireframe: "Show only wireframes of models." -}; - -render.device = { - pc: [1920,1080], - macbook_m2: [2560,1664, 13.6], - ds_top: [400,240, 3.53], - ds_bottom: [320,240, 3.02], - playdate: [400,240,2.7], - switch: [1280,720, 6.2], - switch_lite: [1280,720,5.5], - switch_oled: [1280,720,7], - dsi: [256,192,3.268], - ds: [256,192, 3], - dsixl: [256,192,4.2], - ipad_air_m2: [2360,1640, 11.97], - iphone_se: [1334, 750, 4.7], - iphone_12_pro: [2532,1170,6.06], - iphone_15: [2556,1179,6.1], - gba: [240,160,2.9], - gameboy: [160,144,2.48], - gbc: [160,144,2.28], - steamdeck: [1280,800,7], - vita: [960,544,5], - psp: [480,272,4.3], - imac_m3: [4480,2520,23.5], - macbook_pro_m3: [3024,1964, 14.2], - ps1: [320,240,5], - ps2: [640,480], - snes: [256,224], - gamecube: [640,480], - n64: [320,240], - c64: [320,200], - macintosh: [512,342,9], - gamegear: [160,144,3.2], -}; - -render.device.doc = `Device resolutions given as [x,y,inches diagonal].`; - -global.mixin(use("scripts/physics.js")); -global.mixin(use("scripts/input.js")); -global.mixin(use("scripts/sound.js")); -global.mixin(use("scripts/ai.js")); -global.mixin(use("scripts/geometry.js")); +global.mixin("scripts/tween.js"); +global.mixin("scripts/render.js"); +global.mixin("scripts/physics.js"); +global.mixin("scripts/input.js"); +global.mixin("scripts/sound.js"); +global.mixin("scripts/ai.js"); +global.mixin("scripts/geometry.js"); var Register = { kbm_input(mode, btn, state, ...args) { @@ -194,15 +155,15 @@ var Register = { switch(mode) { case "emacs": - Player.players[0].raw_input(btn, state, ...args); + player[0].raw_input(btn, state, ...args); break; case "mouse": - Player.players[0].mouse_input(btn, state, ...args); + player[0].mouse_input(btn, state, ...args); break; case "char": - Player.players[0].char_input(btn); + player[0].char_input(btn); break; }; }, @@ -275,7 +236,7 @@ Register.add_cb(10, "draw"); register(9, console.stack, this); -Register.gamepad_playermap[0] = Player.players[0]; +Register.gamepad_playermap[0] = player[0]; var Event = { events: {}, @@ -320,21 +281,21 @@ var Window = { }; Window.screen2world = function(screenpos) { -// if (Game.camera) -// return Game.camera.view2world(screenpos); + if (Game.camera) + return Game.camera.view2world(screenpos); + return screenpos; } Window.world2screen = function(worldpos) { - return worldpos; return Game.camera.world2view(worldpos); } Window.icon = function(path) { cmd(90, path); }; Window.icon.doc = "Set the icon of the window using the PNG image at path."; -global.mixin(use("scripts/debug.js")); -global.mixin(use("scripts/spline.js")); -global.mixin(use("scripts/components.js")); +global.mixin("scripts/debug.js"); +global.mixin("scripts/spline.js"); +global.mixin("scripts/components.js"); var Game = { engine_start(fn) { @@ -420,31 +381,33 @@ Window.doc.boundingbox = "Boundingbox of the window, with top and right being it Register.update.register(Game.exec, Game); -global.mixin(use("scripts/entity.js")); +global.mixin("scripts/actor.js"); +global.mixin("scripts/entity.js"); function world_start() { - globalThis.Primum = Object.create(gameobject); - Primum.objects = {}; - Primum.check_dirty = function() {}; - Primum.namestr = function(){}; - Primum._ed = { + globalThis.world = Object.create(gameobject); + world.objects = {}; + world.check_dirty = function() {}; + world.namestr = function(){}; + world._ed = { selectable:false, dirty:false, }; - Primum.toString = function() { return "Primum"; }; - Primum.ur = "Primum"; - Primum.kill = function() { this.clear(); }; - Primum.phys = 2; + world.toString = function() { return "world"; }; + world.master = gameobject; + world.ur = "world"; + world.kill = function() { this.clear(); }; + world.phys = 2; - gameobject.level = Primum; + gameobject.level = world; gameobject.body = make_gameobject(); cmd(113,gameobject.body, gameobject); Object.hide(gameobject, 'timescale'); - - global.world = Primum; + var cam = world.spawn("scripts/camera2d.jso"); + Game.view_camera(cam); } -global.mixin(use("scripts/physics.js")); +global.mixin("scripts/physics.js"); Game.view_camera = function(cam) { @@ -452,8 +415,6 @@ Game.view_camera = function(cam) cmd(61, Game.camera.body); } -prototypes.generate_ur('scripts/camera.jso'); - Window.title(`Prosperon v${prosperon.version}`); Window.width = 1280; Window.height = 720; diff --git a/scripts/entity.js b/scripts/entity.js index 42b1594..82964a7 100644 --- a/scripts/entity.js +++ b/scripts/entity.js @@ -1,4 +1,4 @@ -prosperon.obj_unique_name = function(name, obj) +function obj_unique_name(name, obj) { name = name.replaceAll('.', '_'); if (!(name in obj)) return name; @@ -11,92 +11,55 @@ prosperon.obj_unique_name = function(name, obj) return n; } -var actor = {}; -var a_db = {}; - -actor.spawn = function(script, config){ - if (typeof script !== 'string') return undefined; - if (!a_db[script]) a_db[script] = io.slurp(script); - var padawan = Object.create(actor); - eval_env(a_db[script], padawan); - - if (typeof config === 'object') - Object.merge(padawan,config); - - padawan.padawans = []; - padawan.timers = []; - padawan.master = this; - Object.hide(padawan, "master","timers", "padawans"); - this.padawans.push(padawan); - return padawan; -}; - -actor.spawn.doc = `Create a new actor, using this actor as the master, initializing it with 'script' and with data (as a JSON or Nota file) from 'config'.`; - -actor.timers = []; -actor.kill = function(){ - if (this.__dead__) return; - this.timers.forEach(t => t.kill()); - if (this.master) - delete this.master[this.toString()]; - this.padawans.forEach(p => p.kill()); - this.padawans = []; - this.__dead__ = true; - if (typeof this.die === 'function') this.die(); -}; - -actor.kill.doc = `Remove this actor and all its padawans from existence.`; - -actor.delay = function(fn, seconds) { - var t = Object.create(timer); - t.remain = seconds; - t.kill = () => { - timer.kill.call(t); - delete this.timers[t.toString()]; - } - t.fire = () => { - if (this.__dead__) return; - fn(); - t.kill(); - }; - Register.appupdate.register(t.update, t); - this.timers.push(t); - return function() { t.kill(); }; -}; - -actor.delay.doc = `Call 'fn' after 'seconds' with 'this' set to the actor.`; - -actor.master = undefined; - -actor.padawans = []; - -actor.remaster = function(to){ - delete this.master.padawans[this.toString()]; - this.master = to; - to.padawans.push(this); -}; - -global.app = Object.create(actor); - -app.die = function() +function check_registers(obj) { - Game.quit(); + if (typeof obj.update === 'function') + obj.timers.push(Register.update.register(obj.update.bind(obj))); + + if (typeof obj.physupdate === 'function') + obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj))); + + if (typeof obj.collide === 'function') + register_collide(0, obj.collide.bind(obj), obj.body); + + if (typeof obj.separate === 'function') + register_collide(3,obj.separate.bind(obj), obj.body); + + if (typeof obj.draw === 'function') + obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj)); + + if (typeof obj.debug === 'function') + obj.timers.push(Register.debug.register(obj.debug.bind(obj))); + + if (typeof obj.gui === 'function') + obj.timers.push(Register.gui.register(obj.gui.bind(obj))); + + for (var k in obj) { + if (!k.startswith("on_")) continue; + var signal = k.fromfirst("on_"); + Event.observe(signal, obj, obj[k]); + }; + + obj.components.forEach(function(x) { + if (typeof x.collide === 'function') + register_collide(1, x.collide.bind(x), obj.body, x.shape); + }); } var gameobject_impl = { get pos() { - Debug.assert(this.level, `Entity ${this.toString()} has no level.`); - return this.level.world2this(this.worldpos()); + Debug.assert(this.master, `Entity ${this.toString()} has no master.`); + return this.master.world2this(this.worldpos()); }, set pos(x) { - Debug.assert(this.level, `Entity ${this.toString()} has no level.`); - this.set_worldpos(this.level.this2world(x)); + Debug.assert(this.master, `Entity ${this.toString()} has no master.`); + this.set_worldpos(this.master.this2world(x)); }, get angle() { - Debug.assert(this.level, `No level set on ${this.toString()}`); - return this.worldangle() - this.level.worldangle(); + Debug.assert(this.master, `No master set on ${this.toString()}`); + return this.worldangle() - this.master.worldangle(); }, set angle(x) { @@ -107,18 +70,18 @@ var gameobject_impl = { x.pos = Vector.rotate(x.pos, diff); }); - this.sworldangle(x-this.level.worldangle()); + this.sworldangle(x-this.master.worldangle()); }, get scale() { - Debug.assert(this.level, `No level set on ${this.toString()}`); - var pscale; - if (typeof this.__proto__.scale === 'object') - pscale = this.__proto__.scale; + Debug.assert(this.master, `No master set on ${this.toString()}`); + var pscale = [1,1,1]; +/* if (typeof this.master.scale === 'object') + pscale = this.master.scale; else pscale = [1,1,1]; - - return this.gscale().map((x,i) => x/(this.level.gscale()[i]*pscale[i])); +*/ + return this.gscale().map((x,i) => x/(this.master.gscale()[i]*pscale[i])); }, set scale(x) { @@ -187,9 +150,11 @@ var gameobject = { return undefined; }, check_dirty() { + // TODO: IMPLEMENT + return; this._ed.urdiff = this.json_obj(); this._ed.dirty = !Object.empty(this._ed.urdiff); - var lur = ur[this.level.ur]; + var lur = ur[this.master.ur]; if (!lur) return; var lur = lur.objects[this.toString()]; var d = ediff(this._ed.urdiff,lur); @@ -202,6 +167,7 @@ var gameobject = { selectable: false, dirty: false }, + namestr() { var s = this.toString(); if (this._ed.dirty) @@ -209,8 +175,14 @@ var gameobject = { else s += "*"; return s; }, + + urstr() { + if (this._ed.dirty) return "*"+this.ur; + return this.ur; + }, + full_path() { - return this.path_from(Primum); + return this.path_from(world); }, /* pin this object to the to object */ pin(to) { @@ -270,12 +242,12 @@ var gameobject = { path_from(o) { var p = this.toString(); - var c = this.level; - while (c && c !== o && c !== Primum) { + var c = this.master; + while (c && c !== o && c !== world) { p = c.toString() + "." + p; - c = c.level; + c = c.master; } - if (c === Primum) p = "Primum." + p; + if (c === world) p = "world." + p; return p; }, @@ -334,37 +306,110 @@ var gameobject = { worldangle() { return Math.rad2turn(q_body(2,this.body)); }, sworldangle(x) { set_body(0,this.body,Math.turn2rad(x)); }, - spawn_from_instance(inst) { - return this.spawn(inst.ur, inst); - }, - - spawn(ur, data) { - ur ??= gameobject; - if (typeof ur === 'string') { - //ur = prototypes.get_ur(ur); - - } + /* spawn an entity + text can be: + the file path of a script + an ur object + nothing + */ + spawn(text) { + var ent = Object.create(gameobject); + + if (typeof text === 'object') + text = text.name; - var go = ur.make(this, data); - Object.hide(this, go.toString()); - return go; + if (typeof text === 'undefined') + ent.ur = "new"; + else if (typeof text !== 'string') { + console.error(`Must pass in an ur type or a string to make an entity.`); + return; + } else { + if (Object.access(ur,text)) + ent.ur = text; + else if (io.exists(text)) + ent.ur = "script"; + else { + console.warn(`Cannot make an entity from '${text}'. Not a valid ur.`); + return; + } + } + + Object.mixin(ent,gameobject_impl); + ent.body = make_gameobject(); + + ent.components = {}; + ent.objects = {}; + ent.timers = []; + + ent.reparent(this); + + ent._ed = { + selectable: true, + dirty: false, + inst: false, + urdiff: {}, + }; + + cmd(113, ent.body, ent); // set the internal obj reference to this obj + + Object.hide(ent, 'ur','body', 'components', 'objects', '_ed', 'timers', 'master'); + + if (ent.ur === 'script') + eval_env(io.slurp(text), ent, ent.ur); + else if (ent.ur !== 'new') + apply_ur(ent.ur, ent); + + for (var [prop,p] of Object.entries(ent)) { + if (!p) continue; + if (typeof p !== 'object') continue; + if (component.isComponent(p)) continue; + if (!p.comp) continue; + ent[prop] = component[p.comp].make(ent); + Object.merge(ent[prop], p); + ent.components[prop] = ent[prop]; + }; + + + check_registers(ent); + + if (typeof ent.load === 'function') ent.load(); + if (typeof ent.start === 'function') ent.start(); + + var mur = Object.access(ur,ent.ur); + if (mur && !mur.proto) + mur.proto = json.decode(json.encode(ent)); + + if (!Object.empty(ent.objects)) { + var o = ent.objects; + delete ent.objects; + for (var i in o) { + say(`MAKING ${i}`); + var n = ent.spawn(ur[o[i].ur]); + ent.rename_obj(n.toString(), i); + delete o[i].ur; + Object.assign(n, o[i]); + } + } + + return ent; }, /* Reparent 'this' to be 'parent's child */ reparent(parent) { Debug.assert(parent, `Tried to reparent ${this.toString()} to nothing.`); - if (this.level === parent) { + if (this.master === parent) { console.warn("not reparenting ..."); - console.warn(`${this.level} is the same as ${parent}`); + console.warn(`${this.master} is the same as ${parent}`); return; } - this.level?.remove_obj(this); + this.master?.remove_obj(this); - this.level = parent; + this.master = parent; - function unique_name(list, obj) { - var str = obj.toString().replaceAll('.', '_'); + function unique_name(list, name) { + name ??= "new_object"; + var str = name.replaceAll('.', '_'); var n = 1; var t = str; while (t in list) { @@ -374,7 +419,7 @@ var gameobject = { return t; }; - var name = unique_name(parent, this.ur); + var name = unique_name(Object.keys(parent.objects), this.ur); parent.objects[name] = this; parent[name] = this; @@ -391,7 +436,7 @@ var gameobject = { components: {}, objects: {}, - level: undefined, + master: undefined, pulse(vec) { set_body(4, this.body, vec);}, shove(vec) { set_body(12,this.body,vec);}, @@ -427,7 +472,7 @@ 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()]; + var lobj = this.master.__proto__.objects[this.toString()]; delete jobj.objects; Object.keys(jobj).forEach(function(x) { if (lobj && x in lobj) @@ -438,53 +483,15 @@ var gameobject = { this.sync(); }, - unregister() { - this.timers.forEach(t=>t()); - this.timers = []; - }, - - check_registers(obj) { - obj.unregister(); - - if (typeof obj.update === 'function') - obj.timers.push(Register.update.register(obj.update.bind(obj))); - - if (typeof obj.physupdate === 'function') - obj.timers.push(Register.physupdate.register(obj.physupdate.bind(obj))); - - if (typeof obj.collide === 'function') - register_collide(0, obj.collide.bind(obj), obj.body); - - if (typeof obj.separate === 'function') - register_collide(3,obj.separate.bind(obj), obj.body); - - if (typeof obj.draw === 'function') - obj.timers.push(Register.draw.register(obj.draw.bind(obj), obj)); - - if (typeof obj.debug === 'function') - obj.timers.push(Register.debug.register(obj.debug.bind(obj))); - - if (typeof obj.gui === 'function') - obj.timers.push(Register.gui.register(obj.gui.bind(obj))); - - for (var k in obj) { - if (!k.startswith("on_")) continue; - var signal = k.fromfirst("on_"); - Event.observe(signal, obj, obj[k]); - }; - - obj.components.forEach(function(x) { - if (typeof x.collide === 'function') - register_collide(1, x.collide.bind(x), obj.body, x.shape); - }); - }, toString() { return "new_object"; }, - flipx() { return this.scale.x < 0; }, - flipy() { return this.scale.y < 0; }, - mirror(plane) { - this.scale = Vector.reflect(this.scale, plane); - }, + flipx() { return this.scale.x < 0; }, + flipy() { return this.scale.y < 0; }, + + mirror(plane) { + this.scale = Vector.reflect(this.scale, plane); + }, + save:true, selectable:true, ed_locked:false, @@ -524,17 +531,22 @@ var gameobject = { /* The unique components of this object. Its diff. */ json_obj() { - var d = ediff(this,this.__proto__); + var u = Object.access(ur,this.ur); + if (!u) return {}; + var proto = u.proto; + var thiso = json.decode(json.encode(this)); // TODO: SLOW. Used to ignore properties in toJSON of components. + + var d = ediff(thiso,proto); d ??= {}; var objects = {}; - this.__proto__.objects ??= {}; + proto.objects ??= {}; var curobjs = {}; for (var o in this.objects) curobjs[o] = this.objects[o].instance_obj(); - var odiff = ediff(curobjs, this.__proto__.objects); + var odiff = ediff(curobjs, proto.objects); if (odiff) d.objects = curobjs; @@ -546,15 +558,19 @@ var gameobject = { return d; }, - /* The object needed to store an object as an instance of a level */ + /* The object needed to store an object as an instance of a master */ instance_obj() { var t = this.transform(); -// var j = this.json_obj(); -// Object.assign(t,j); t.ur = this.ur; return t; }, + proto() { + var u = Object.access(ur,this.ur); + if (!u) return {}; + return u.proto; + }, + transform() { var t = {}; t.pos = this.pos; @@ -562,7 +578,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.__proto__.scale[i]); + t.scale = t.scale.map((x,i) => x/this.proto().scale[i]); t.scale = t.scale.map(x => Math.places(x,3)); if (t.scale.every(x=>x===1)) delete t.scale; return t; @@ -577,7 +593,7 @@ var gameobject = { }, dup(diff) { - var n = this.level.spawn(this.__proto__); + var n = this.master.spawn(this.__proto__); Object.totalmerge(n, this.instance_obj()); return n; }, @@ -592,9 +608,9 @@ var gameobject = { Player.do_uncontrol(this); register_collide(2, undefined, this.body); - if (this.level) { - this.level.remove_obj(this); - this.level = undefined; + if (this.master) { + this.master.remove_obj(this); + this.master = undefined; } if (this.__proto__.instances) @@ -608,10 +624,9 @@ var gameobject = { this.clear(); this.objects = undefined; - - - if (typeof this.stop === 'function') - this.stop(); + + if (typeof this.stop === 'function') this.stop(); + if (typeof this.die === 'function') this.die(); }, up() { return [0,1].rotate(this.angle);}, @@ -619,63 +634,10 @@ var gameobject = { right() { return [1,0].rotate(this.angle);}, left() { return [-1,0].rotate(this.angle); }, - make() { - var obj = Object.create(this); - - obj.make = undefined; - Object.mixin(obj,gameobject_impl); - - obj.body = make_gameobject(); - - obj.components = {}; - obj.objects = {}; - obj.timers = []; - - obj._ed = { - selectable: true, - dirty: false, - inst: false, - urdiff: {}, - }; - - obj.ur = this.toString(); - obj.level = undefined; - - obj.reparent(level); - - cmd(113, obj.body, obj); // set the internal obj reference to this obj - - for (var [prop,p] of Object.entries(this)) { - if (!p) continue; - if (component.isComponent(p)) { - obj[prop] = p.make(obj); - obj.components[prop] = obj[prop]; - } - }; - - Object.hide(obj, 'ur','body', 'components', 'objects', '_ed', 'level', 'timers'); - - if (this.objects) - obj.make_objs(this.objects) - - Object.dainty_assign(obj, this); - obj.sync(); - gameobject.check_registers(obj); - - if (data) - Object.dainty_assign(obj,data); - - if (typeof obj.load === 'function') obj.load(); - if (Game.playing() && typeof obj.start === 'function') obj.start(); - - return obj; - }, - make_objs(objs) { for (var prop in objs) { - var newobj = this.spawn_from_instance(objs[prop]); - if (!newobj) continue; - this.rename_obj(newobj.toString(), prop); + say(`spawning ${json.encode(objs[prop])}`); + var newobj = this.spawn(objs[prop]); } }, @@ -700,10 +662,11 @@ var gameobject = { return this.objects[newname]; }, - add_component(comp, data) { + add_component(comp, data, name) { data ??= undefined; if (typeof comp.make !== 'function') return; - var name = prosperon.obj_unique_name(comp.toString(), this); + name ??= comp.toString(); + name = obj_unique_name(name, this); this[name] = comp.make(this); this[name].comp = comp.toString(); this.components[name] = this[name]; @@ -724,11 +687,11 @@ gameobject.spawn.doc = `Spawn an entity of type 'ur' on this entity. Returns the gameobject.doc = { doc: "All objects in the game created through spawning have these attributes.", - pos: "Position of the object, relative to its level.", - angle: "Rotation of this object, relative to its level.", + pos: "Position of the object, relative to its master.", + angle: "Rotation of this object, relative to its master.", velocity: "Velocity of the object, relative to world.", angularvelocity: "Angular velocity of the object, relative to the world.", - scale: "Scale of the object, relative to its level.", + scale: "Scale of the object, relative to its master.", flipx: "Check if the object is flipped on its x axis.", flipy: "Check if the object is flipped on its y axis.", elasticity: `When two objects collide, their elasticities are multiplied together. Their velocities are then multiplied by this value to find their resultant velocities.`, @@ -757,7 +720,7 @@ gameobject.doc = { dup: `Make an exact copy of this object.`, transform: `Return an object representing the transform state of this object.`, kill: `Remove this object from the world.`, - level: "The entity this entity belongs to.", + master: "The entity this entity belongs to.", delay: 'Run the given function after the given number of seconds has elapsed.', cry: 'Make a sound. Can only make one at a time.', add_component: 'Add a component to the object by name.', @@ -773,167 +736,7 @@ gameobject.doc = { motor: 'Keeps the relative angular velocity of this body to to at a constant rate. The most simple idea is for one of the bodies to be static, to the other is kept at rate.' }; -/* Default objects */ -var prototypes = {}; -prototypes.ur_ext = ".jso"; -prototypes.ur = {}; - -/* Makes a new ur-type from disk. If the ur doesn't exist, it searches on the disk to create it. */ -prototypes.from_file = function(file) -{ - var urpath = file; - var path = urpath.split('.'); - if (path.length > 1 && (path.at(-1) === path.at(-2))) { - urpath = path.slice(0,-1).join('.'); - return prototypes.get_ur(urpath); - } - - var upperur = gameobject; - - if (path.length > 1) { - var upur = undefined; - var upperpath = path.slice(0,-1); - while (!upur && upperpath) { - upur = prototypes.get_ur(upperpath.join('/')); - upperpath = upperpath.slice(0,-1); - } - if (upur) upperur = upur; - } - - var newur = {}; - - file = file.replaceAll('.','/'); - - var jsfile = prototypes.get_ur_file(urpath, prototypes.ur_ext); - var jsonfile = prototypes.get_ur_file(urpath, ".json"); - - var script = undefined; - var json = undefined; - - if (jsfile) script = io.slurp(jsfile); - try { - if (jsonfile) json = JSON.parse(io.slurp(jsonfile)); - } catch(e) { - console.warn(`Unable to create json from ${jsonfile}. ${e}`); - } - - if (!json && !jsfile) { - console.warn(`Could not make ur from ${file}`); - return undefined; - } - - if (script) - load_env(jsfile, newur); - - json ??= {}; - Object.merge(newur,json); - - Object.entries(newur).forEach(function([k,v]) { - if (Object.isObject(v) && Object.isObject(upperur[k])) - v.__proto__ = upperur[k]; - }); - - Object.values(newur).forEach(function(v) { - if (typeof v !== 'object') return; - if (!v.comp) return; - v.__proto__ = component[v.comp]; - }); - - newur.__proto__ = upperur; - newur.instances = []; - Object.hide(newur, 'instances'); - - prototypes.list.push(urpath); - newur.toString = function() { return urpath; }; - ur[urpath] = newur; - - return newur; -} -prototypes.from_file.doc = "Create a new ur-type from a given script file."; -prototypes.list = []; - -prototypes.list_ur = function() -{ - var list = []; - function list_obj(obj, prefix) - { - prefix ??= ""; - var list = []; - for (var e in obj) { - list.push(prefix + e); - list.concat(list_obj(obj[e], e + ".")); - } - - return list; - } - - return list_obj(ur); -} - -prototypes.ur2file = function(urpath) -{ - return urpath.replaceAll('.', '/'); -} - -prototypes.file2ur = function(file) -{ - file = file.strip_ext(); - file = file.replaceAll('/','.'); - return file; -} - -prototypes.get_ur = function(name) -{ - if (!name) return; - if (!name) { - console.error(`Can't get ur from ${name}.`); - return; - } - var urpath = name; - if (urpath.includes('/')) - urpath = prototypes.file2ur(name); - - if (!prototypes.ur[urpath]) { - var ur = prototypes.from_file(urpath); - if (ur) - return ur; - else { - console.warn(`Could not find prototype using name ${name}.`); - return undefined; - } - } else - return prototypes.ur[urpath]; -} - -prototypes.get_ur.doc = `Returns an ur, or makes it, for any given type of path - could be a file on a disk like ball/big.js - could be an ur path like ball.big`; - -prototypes.get_ur_file = function(path, ext) -{ - var urpath = prototypes.ur2file(path); - var file = urpath + ext; - if (io.exists(file)) return file; - file = urpath + "/" + path.split('.').at(-1) + ext; - if (io.exists(file)) return file; - return undefined; -} - -prototypes.generate_ur = function(path) -{ - var ob = io.glob("**" + prototypes.ur_ext); - ob = ob.concat(io.glob("**.json")); - - ob = ob.map(function(path) { return path.set_ext(""); }); - ob = ob.map(function(path) { return path[0] !== '.' ? path : undefined; }); - ob = ob.map(function(path) { return path[0] !== '_' ? path : undefined; }); - ob = ob.filter(x => x !== undefined); - ob.forEach(function(name) { prototypes.get_ur(name); }); -} - -var ur = prototypes.ur; - -prototypes.resavi = function(ur, path) +var resavi = function(ur, path) { if (!ur) return path; if (path[0] === '/') return path; @@ -946,7 +749,7 @@ prototypes.resavi = function(ur, path) return path; } -prototypes.resani = function(ur, path) +var resani = function(ur, path) { if (!path) return ""; if (!ur) return path; @@ -964,57 +767,88 @@ prototypes.resani = function(ur, path) return restry; } -prototypes.ur_dir = function(ur) -{ - var path = ur.replaceAll('.', '/'); - console.warn(path); - console.warn(io.exists(path)); - console.warn(`${path} does not exist; sending ${path.dir()}`); +var ur = {}; +ur._list = []; + +/* UR OBJECT +ur { + name: fully qualified name of ur + text: file path to the script + data: file path to data + proto: resultant object of a freshly made entity } +*/ -prototypes.ur_json = function(ur) +/* Apply an ur u to an entity e */ +/* u is given as */ +function apply_ur(u, e) { - var path = ur.replaceAll('.', '/'); - if (io.exists(path)) - path = path + "/" + path.name() + ".json"; - else - path = path + ".json"; - - return path; -} - -prototypes.ur_stem = function(ur) -{ - var path = ur.replaceAll('.', '/'); - if (io.exists(path)) - return path + "/" + path.name(); - else - return path; -} - -prototypes.ur_file_exts = ['.jso', '.json']; - -prototypes.ur_folder = function(ur) -{ - var path = ur.replaceAll('.', '/'); - return io.exists(path); -} - -prototypes.ur_pullout_folder = function(ur) -{ - if (!prototypes.ur_folder(ur)) return; - - var stem = prototypes.ur_stem(ur); + if (typeof u !== 'string') { + console.warn("Must give u as a string."); + return; + } -/* prototypes.ur_file_exts.forEach(function(e) { - var p = stem + e; - if (io.exists(p)) - */ + var urs = u.split('.'); + var config = {}; + var topur = ur; + for (var i = 0; i < urs.length; i++) { + topur = topur[urs[i]]; + if (!topur) { + console.warn(`Ur given by ${u} does not exist. Stopped at ${urs[i]}.`); + return; + } + + if (topur.text) + feval_env(topur.text, e); + + if (topur.data) + Object.merge(config, json.decode(io.slurp(topur.data))); + } + + Object.merge(e, config); +} + +function file2fqn(file) +{ + var fqn = file.strip_ext(); + if (fqn.folder_same_name()) + fqn = fqn.up_path(); + + fqn = fqn.replace('/','.'); + var topur; + if (topur = Object.access(ur,fqn)) return topur; + + var fqnlast = fqn.split('.').last(); + + if (topur = Object.access(ur,fqn.tolast('.'))) { + topur[fqnlast] = { + name: fqn + }; + ur._list.push(fqn); + return Object.access(ur,fqn); + } + + fqn = fqnlast; + + ur[fqn] = { + name: fqn + }; + ur._list.push(fqn); + return ur[fqn]; +} + +/* FIND ALL URS IN A PROJECT */ +for (var file of io.glob("**.jso")) { + var topur = file2fqn(file); + topur.text = file; +} + +for (var file of io.glob("**.json")) { + var topur = file2fqn(file); + topur.data = file; } return { gameobject, - actor, - prototypes, ur } diff --git a/scripts/input.js b/scripts/input.js index 960b7d9..6c4f2fa 100644 --- a/scripts/input.js +++ b/scripts/input.js @@ -203,6 +203,7 @@ var Player = { n.pawns = []; n.gamepads = []; this.players.push(n); + this[this.players.length-1] = n; return n; }, @@ -227,9 +228,12 @@ Player.print_pawns.doc = "Print out a list of the current pawn control stack."; Player.doc = {}; Player.doc.players = "A list of current players."; +var player = Player; + return { Mouse, Keys, input, Player, + player }; diff --git a/scripts/render.js b/scripts/render.js new file mode 100644 index 0000000..8d7c9eb --- /dev/null +++ b/scripts/render.js @@ -0,0 +1,119 @@ +var render = { + normal() { cmd(67);}, + wireframe() { cmd(68); }, + pass() { }, +}; + +render.doc = { + doc: "Functions for rendering modes.", + normal: "Final render with all lighting.", + wireframe: "Show only wireframes of models." +}; + +render.device = { + pc: [1920,1080], + macbook_m2: [2560,1664, 13.6], + ds_top: [400,240, 3.53], + ds_bottom: [320,240, 3.02], + playdate: [400,240,2.7], + switch: [1280,720, 6.2], + switch_lite: [1280,720,5.5], + switch_oled: [1280,720,7], + dsi: [256,192,3.268], + ds: [256,192, 3], + dsixl: [256,192,4.2], + ipad_air_m2: [2360,1640, 11.97], + iphone_se: [1334, 750, 4.7], + iphone_12_pro: [2532,1170,6.06], + iphone_15: [2556,1179,6.1], + gba: [240,160,2.9], + gameboy: [160,144,2.48], + gbc: [160,144,2.28], + steamdeck: [1280,800,7], + vita: [960,544,5], + psp: [480,272,4.3], + imac_m3: [4480,2520,23.5], + macbook_pro_m3: [3024,1964, 14.2], + ps1: [320,240,5], + ps2: [640,480], + snes: [256,224], + gamecube: [640,480], + n64: [320,240], + c64: [320,200], + macintosh: [512,342,9], + gamegear: [160,144,3.2], +}; + +render.device.doc = `Device resolutions given as [x,y,inches diagonal].`; + +/* All draw in screen space */ +render.point = function(pos,size,color) { + color ??= Color.blue; + render.circle(pos,size,color); + }; + +render.line = function(points, color, thickness) { + thickness ??= 1; + color ??= Color.white; + cmd(83, points, color, thickness); + }; + +render.poly = function(points, color) { cmd_points(0,points,color); }; + +render.circle = function(pos, radius, color) { cmd(115, pos, radius, color); }; + +render.cross = function(pos, size, color) { + color ??= Color.red; + var a = [ + pos.add([0,size]), + pos.add([0,-size]) + ]; + var b = [ + pos.add([size,0]), + pos.add([-size,0]) + ]; + + render.line(a,color); + render.line(b,color); + }; + +render.arrow = function(start, end, color, wingspan, wingangle) { + color ??= Color.red; + wingspan ??= 4; + wingangle ??=10; + + var dir = end.sub(start).normalized(); + var wing1 = [ + Vector.rotate(dir, wingangle).scale(wingspan).add(end), + end + ]; + var wing2 = [ + Vector.rotate(dir,-wingangle).scale(wingspan).add(end), + end + ]; + render.line([start,end],color); + render.line(wing1,color); + render.line(wing2,color); + }; + +render.rectangle = function(lowerleft, upperright, color) { + var pos = lowerleft.add(upperright).map(x=>x/2); + var wh = [upperright.x-lowerleft.x,upperright.y-lowerleft.y]; + render.box(pos,wh,color); + }; + +render.box = function(pos, wh, color) { + color ??= Color.white; + cmd(53, pos, wh, color); + }; + +render.doc = "Draw shapes in screen space."; +render.circle.doc = "Draw a circle at pos, with a given radius and color."; +render.cross.doc = "Draw a cross centered at pos, with arm length size."; +render.arrow.doc = "Draw an arrow from start to end, with wings of length wingspan at angle wingangle."; +render.poly.doc = "Draw a concave polygon from a set of points."; +render.rectangle.doc = "Draw a rectangle, with its corners at lowerleft and upperright."; +render.box.doc = "Draw a box centered at pos, with width and height in the tuple wh."; +render.line.doc = "Draw a line from a set of points, and a given thickness."; + +return {render}; diff --git a/scripts/sound.js b/scripts/sound.js index 2b15a53..e5d51dd 100644 --- a/scripts/sound.js +++ b/scripts/sound.js @@ -113,3 +113,5 @@ Object.mixin(cmd(180).__proto__, { pct() { return this.time()/this.length(); }, }); */ + +return {audio}; diff --git a/scripts/spline.js b/scripts/spline.js new file mode 100644 index 0000000..fb43bce --- /dev/null +++ b/scripts/spline.js @@ -0,0 +1,112 @@ +var Spline = {}; +Spline.sample_angle = function(type, points, angle) { + return spline_cmd(0, type, points[0].length, points, angle); +} + +Spline.bezier_loop = function(cp) +{ + cp.push(Vector.reflect_point(cp.at(-2),cp.at(-1))); + cp.push(Vector.reflect_point(cp[1],cp[0])); + cp.push(cp[0].slice()); + return cp; +} + +Spline.bezier_node_count = function(cp) +{ + if (cp.length === 4) return 2; + return 2 + (cp.length-4)/3; +} + +Spline.is_bezier = function(t) { return t === Spline.type.bezier; } +Spline.is_catmull = function(t) { return t === Spline.type.catmull; } + +Spline.bezier2catmull = function(b) +{ + var c = []; + for (var i = 0; i < b.length; i += 3) + c.push(b[i]); + return c; +} + +Spline.catmull2bezier = function(c) +{ + var b = []; + for (var i = 1; i < c.length-2; i++) { + b.push(c[i].slice()); + b.push(c[i+1].sub(c[i-1]).scale(0.25).add(c[i])); + b.push(c[i].sub(c[i+2]).scale(0.25).add(c[i+1])); + } + b.push(c[c.length-2]); + return b; +} + +Spline.catmull_loop = function(cp) +{ + cp = cp.slice(); + cp.unshift(cp.last()); + cp.push(cp[1]); + cp.push(cp[2]); + return cp; +} + +Spline.catmull_caps = function(cp) +{ + cp = cp.slice(); + cp.unshift(cp[0].sub(cp[1]).add(cp[0])); + cp.push(cp.last().sub(cp.at(-2).add(cp.last()))); + return cp; +} + +Spline.catmull2bezier.doc = "Given a set of control points C for a camtull-rom type curve, return a set of cubic bezier points to give the same curve." + +Spline.type = { + catmull: 0, + bezier: 1, + bspline: 2, + cubichermite: 3 +}; + +Spline.bezier_tan_partner = function(points, i) +{ + if (i%3 === 0) return undefined; + var partner_i = (i%3) === 2 ? i-1 : i+1; + return points[i]; +} + +Spline.bezier_cp_mirror = function(points, i) +{ + if (i%3 === 0) return undefined; + var partner_i = (i%3) === 2 ? i+2 : i-2; + var node_i = (i%3) === 2 ? i+1 : i-1; + if (partner_i >= points.length || node_i >= points.length) return; + points[partner_i] = points[node_i].sub(points[i]).add(points[node_i]); +} + +Spline.bezier_point_handles = function(points, i) +{ + if (!Spline.bezier_is_node(points,i)) return []; + var a = i-1; + var b = i+1; + var c = [] + if (a > 0) + c.push(a); + + if (b < points.length) + c.push(b); + + return c; +} + +Spline.bezier_nodes = function(points) +{ + var c = []; + for (var i = 0; i < points.length; i+=3) + c.push(points[i].slice()); + + return c; +} + +Spline.bezier_is_node = function(points, i) { return i%3 === 0; } +Spline.bezier_is_handle = function(points, i) { return !Spline.bezier_is_node(points,i); } + +return {Spline}; diff --git a/scripts/std.js b/scripts/std.js index 0187d20..ae3f54e 100644 --- a/scripts/std.js +++ b/scripts/std.js @@ -238,6 +238,10 @@ Cmdline.register_order("init", function() { }, "Turn the directory into a Prosperon game."); +Cmdline.register_order("debug", function() { + Cmdline.orders.play(); +}, "Play the game with debugging enabled."); + Cmdline.register_order("play", function() { if (!io.exists(".prosperon/project")) { say("No game to play. Try making one with 'prosperon init'."); @@ -247,8 +251,8 @@ Cmdline.register_order("play", function() { var project = json.decode(io.slurp(".prosperon/project")); Game.engine_start(function() { - load("config.js"); - load("game.js"); + global.mixin("config.js"); + global.mixin("game.js"); if (project.icon) Window.icon(project.icon); if (project.title) Window.title(project.title); }); @@ -379,35 +383,6 @@ function cmd_args(cmdargs) Cmdline.register_order("clean", function(argv) { say("Cleaning not implemented."); - return; - - var f = argv[0]; - if (argv.length === 0) { - Cmdline.print_order("clean"); - return; - } - - if (!io.exists(f)) { - say(`File ${f} does not exist.`); - return; - } - - prototypes.generate_ur(); - - var j = json.decode(io.slurp(f)); - - for (var k in j) - if (k in j.objects) - delete j[k]; - - console.warn(j); - - for (var k in j.objects) { - var o = j.objects[k]; - samediff(o, ur[o.ur]); - } - - say(j); }, "Clean up a given object file.", "JSON ..."); Cmdline.register_cmd("l", function(n) { diff --git a/scripts/tween.js b/scripts/tween.js index 2c71260..d2f7e95 100644 --- a/scripts/tween.js +++ b/scripts/tween.js @@ -196,3 +196,5 @@ var Tween = { }; Tween.make = Tween.start; + +return {Tween, Ease}; diff --git a/source/engine/jsffi.c b/source/engine/jsffi.c index e09d0d0..c4a77ac 100644 --- a/source/engine/jsffi.c +++ b/source/engine/jsffi.c @@ -212,8 +212,9 @@ double js2number(JSValue v) { void *js2ptr(JSValue v) { return JS_GetOpaque(v,js_ptr_id); } -JSValue float2js(double g) { return JS_NewFloat64(js, g);} -JSValue number2js(double g) { return float2js(g); } +JSValue number2js(double g) { + return JS_NewFloat64(js,g); +} struct sprite *js2sprite(JSValue v) { return id2sprite(js2int(v)); } JSValue ptr2js(void *ptr) { @@ -1085,6 +1086,11 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) ret = number2js(get_timescale()); break; + case 122: + str = JS_ToCString(js, argv[1]); + ret = file_eval_env(str, argv[2]); + break; + case 123: str = JS_ToCString(js, argv[1]); str2 = JS_ToCString(js, argv[3]); @@ -1137,7 +1143,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) break; case 135: - ret = float2js(cam_zoom()); + ret = number2js(cam_zoom()); break; case 136: diff --git a/source/engine/nota.c b/source/engine/nota.c index 330b77c..86ef85d 100644 --- a/source/engine/nota.c +++ b/source/engine/nota.c @@ -117,7 +117,6 @@ char *nota_write_int(long long n, char *nota) return nota_continue_num(n, nota, 3); } - #define NOTA_DBL_PREC 6 #define xstr(s) str(s) #define str(s) #s diff --git a/source/engine/nota.h b/source/engine/nota.h index bccdad5..173e3b5 100644 --- a/source/engine/nota.h +++ b/source/engine/nota.h @@ -11,9 +11,10 @@ #define NOTA_FALSE 0x00 #define NOTA_TRUE 0x01 +#define NOTA_NULL 0x02 +#define NOTA_INF 0x03 #define NOTA_PRIVATE 0x08 #define NOTA_SYSTEM 0x09 -#define NOTA_NULL 0x02 typedef struct NOTA { char *head; diff --git a/source/engine/resources.c b/source/engine/resources.c index bf57e99..3ad22e9 100644 --- a/source/engine/resources.c +++ b/source/engine/resources.c @@ -148,10 +148,8 @@ void *cdb_slurp(struct cdb *cdb, const char *file, size_t *size) int fexists(const char *path) { - return !access(path,R_OK); - int len = strlen(path); - if (cdb_find(&game_cdb, path,len)) return 1; + if (cdb_find(&game_cdb, path, len)) return 1; else if (cdb_find(&corecdb, path, len)) return 1; else if (!access(path, R_OK)) return 1; diff --git a/source/engine/script.c b/source/engine/script.c index 90037b0..8bd6b53 100644 --- a/source/engine/script.c +++ b/source/engine/script.c @@ -58,7 +58,7 @@ void script_startup() { rt = JS_NewRuntime(); js = JS_NewContext(rt); - sh_new_arena(jsstrs); + sh_new_arena(jsstrs); ffi_load(); @@ -231,7 +231,7 @@ JSValue eval_file_env(const char *script, const char *file, JSValue env) return v; } -void file_eval_env(const char *file, JSValue env) +JSValue file_eval_env(const char *file, JSValue env) { size_t len; char *script = slurp_text(file, &len); diff --git a/source/engine/script.h b/source/engine/script.h index 6e3907c..cfd8f6e 100644 --- a/source/engine/script.h +++ b/source/engine/script.h @@ -48,7 +48,7 @@ void call_callee(struct callee *c); void script_callee(struct callee c, int argc, JSValue *argv); int script_has_sym(void *sym); void script_eval_w_env(const char *s, JSValue env, const char *file); -void file_eval_env(const char *file, JSValue env); +JSValue file_eval_env(const char *file, JSValue env); time_t file_mod_secs(const char *file); diff --git a/source/engine/warp.c b/source/engine/warp.c index 57f29da..dfd47b1 100644 --- a/source/engine/warp.c +++ b/source/engine/warp.c @@ -4,16 +4,6 @@ static warp_gravity **warps = NULL; -warp_gravity *warp_gravity_make() -{ - warp_gravity *n = calloc(sizeof(*n),1); - n->strength = 9.8; - n->t.scale = (HMM_Vec3){0,-1,0}; - n->planar_force = HMM_MulV3F(n->t.scale, n->strength); - arrput(warps, n); - return n; -} - warp_damp *warp_damp_make() { warp_damp *d = calloc(sizeof(*d),1); @@ -21,6 +11,17 @@ warp_damp *warp_damp_make() } void warp_damp_free(warp_damp *d) { free(d); } + +warp_gravity *warp_gravity_make() +{ + warp_gravity *n = calloc(sizeof(*n),1); + n->strength = 9.8; + n->t.scale = (HMM_Vec3){0,-1,0}; + n->planar_force = (HMM_Vec3){0,-1,0}; + arrput(warps, n); + return n; +} + void warp_gravity_free(warp_gravity *n) { for (int i = 0; i < arrlen(warps); i++) { if (warps[i] == n) { @@ -47,7 +48,7 @@ HMM_Vec3 warp_gravity_force(warp_gravity *g, HMM_Vec3 pos) HMM_Vec3 norm = HMM_NormV3(HMM_SubV3(g->t.pos, pos)); return HMM_MulV3F(norm,g->strength); } else { - return g->planar_force; + return HMM_MulV3F(g->planar_force, g->strength); } } diff --git a/source/engine/warp.h b/source/engine/warp.h index 57dbe16..c7845e9 100644 --- a/source/engine/warp.h +++ b/source/engine/warp.h @@ -44,5 +44,4 @@ warp_damp *warp_damp_make(); void warp_gravity_free(warp_gravity *g); void warp_damp_free(warp_damp *d); - #endif