Add all files needed for engine into main branch

This commit is contained in:
John Alanbrook 2023-04-22 21:44:26 +00:00
parent 1e8d76961d
commit 2ab0f33d3f
31 changed files with 6077 additions and 17 deletions

View file

@ -114,12 +114,14 @@ $(BIN)$(NAME): $(objprefix)/source/engine/yugine.o $(ENGINE) $(BIN)libquickjs.a
$(CC) $< $(LINK) -o $(BIN)$(NAME) $(CC) $< $(LINK) -o $(BIN)$(NAME)
@echo Finished build @echo Finished build
$(BIN)$(DIST): $(BIN)$(NAME) source/shaders/* $(BIN)$(DIST): $(BIN)$(NAME) source/shaders/* source/scripts/* assets/*
@echo Creating distribution $(DIST) @echo Creating distribution $(DIST)
@mkdir -p $(BIN)dist @mkdir -p $(BIN)dist
@cp $(BIN)$(NAME) $(BIN)dist @cp $(BIN)$(NAME) $(BIN)dist
@cp -rf assets/fonts $(BIN)dist @cp -rf assets/fonts $(BIN)dist
@cp -rf assets/icons $(BIN)dist
@cp -rf source/shaders $(BIN)dist @cp -rf source/shaders $(BIN)dist
@cp -r source/scripts $(BIN)dist
@tar czf $(DIST) --directory $(BIN)dist . @tar czf $(DIST) --directory $(BIN)dist .
@mv $(DIST) $(BIN) @mv $(DIST) $(BIN)

Binary file not shown.

4
assets/fonts/dos.font Normal file
View file

@ -0,0 +1,4 @@
(font
:path "LessPerfectDOSVGA.ttf"
:size 16
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

BIN
assets/icons/no_tex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

164
docs/editor.adoc Normal file
View file

@ -0,0 +1,164 @@
= Yugine Editor
The main editor view is made up of objects. Each object can have a
number of components attached to it. When an object is selected, its
name, position, and list of components are listed.
.Basic controls
* Ctrl-Z Undo
* Ctrl-Shift-Z Redo
* Ctrl-A Select all
* Ctrl-S Save
* Ctrl-N New
* Ctrl-O Open level
* Ctrl-X Cut
* Ctrl-C Copy
* Ctrl-V Paste
* Alt-O Add level to current level
* Alt-A or Alt-P Add a prefab
* Ctrl-I Objects on the level
* Ctrl-M Asset viewer. When on a component like a sprite, serves to select that sprite's texture
* Ctrl-[ Downsize grid
* Ctrl-] Upsize grid
* Backtick REPL
* Ctrl-[1-9] to set camera positions
* [1-9] to recall camera positions
* 0 Set camera to home view
* ESC quit
* Alt-1 Normal view
* Alt-2 Wireframe view
* Shift-Middle Set editor cursor to mouse position (Cursor affects how objects rotate)
* Shift-Ctrl-Middle Set cursor to object selection
* Shift-Right Remove cursor
.Editor Mode select
* Alt-F1 Basic mode
* Alt-F2 Brush mode
- Clicking will place what is on clipboard
.Object controls
* G Translate
* Alt-G Snap objects to cursor
* S Scale
* R Rotate
* Ctrl-P Save object changes to prefab
* Ctrl-shift-P Save object changes as a unique prefab ("Parent")
* Ctrl-shift-T Save object changes to a side prefab ("Type")
* Ctrl-J Bake name to expose to level script
* Alt-J Remove baked name
* Ctrl-Y Show obj chain
* Alt-Y Start prototype explorer
* Ctrl-U Revert object or component to match prototype
* Alt-U Make object unique. If a level, allows setting of internal object position and rotation.
* Ctrl-shift-G Save group as a level
* Arrows Translate 1 px
* Shift-Arrows Translate 10 px
* Tab Select component
* F Zoom to object(s)
* Ctrl-F Focus on a selected sublevel. Edit and save it in place.
* Ctrl-Shift-F Go up one level in the editing chain.
* M Flip horizontally
* Ctrl-M Flip vertically
* Ctrl-D Duplicate
* H Hide
* Ctrl-H Unhide all
* T Lock
* Alt-T Unlock all
* Q Toggle component help
* Ctrl-Shift-Alt-Click Set object center
.Mouse controls
* Left Select
* Middle Quick grab
* Right Unselect
.Level controls
* Ctrl-L Open level script
.Game controls
* F1 Debug draw
* F2 Config menu
* F3 Show bounding boxes
* F4 Show gizmos
* F5 Start
* F6 Pause
* F7 Stop
* F10 Toggle slow motion
== Components
Components all have their own set of controls. Many act similar to
objects. If a component has a position attribute, it will react as
expected to object grabbing; same with scaling, rotation, and so on.
If a component uses an asset, the asset viewer will serve to pick new
assets for it.
.Spline controls
* Ctrl-click Add a point
* Shift-click remove a point
* +,- Increase or decrease spline segments
* Ctrl-+,- Increase or decrease spline degrees. Put this to 1 for the spline to go point to point
* Alt-B,V Increase or decrease spline thickness
.Collider controls
* Alt-S Toggle sensor
= Yugine Programming
.Object functions
* start(): Called when the object is created, before the first update is ran
* update(dt): Called once per frame
* physupdate(dt): Called once per physics calculation
* stop(): Called when the object is killed
* collide(hit): Called when this object collides with another. If on a collider, specific to that collider
- hit.hit: Gameobject ID of what's being hit
- hit.velocity: Velocity of impact
- hit.normal: Normal of impact
.Input
Input works by adding functions to an object, and then "controlling"
them. The format for a function is "input_[key]_[action]". [Action]
can be any of
- down: Called once per frame the key is down
- pressed: Called when the key is pressed
- released: called when the key is released
For example, "input_p_pressed()" will be called when p is pressed, and not again
until it is released and pressed again.
.Your game
When the engine runs, it executes config.js, and then game.js. A
window should be created in config.js, and custom code for prototypes
should be executed.
game.js is the place to open your first level.
.Levels
A level is a collection of objects. A level has a script associated
with it. The script is ran when the level is loaded.
Levels can be added to other levels. Each is independent and unique.
In this way, complicated behavior can easily be added up. For example,
a world might have a door that opens with a lever. The door and lever
can be saved off as their own level, and the level script can contain
the code that causes the door to open when the lever is thrown. Then,
as many door-lever levels can be added to your game as you want.
The two primary ways to add objects to the game are World.spawn, and
Level.add. World.spawn creates a single object in the world, Level.add
adds an entire level, along with its script.
Levels also can be checked for "completion". A level can be loaded
over many frames, and only have all of its contents appear once it's
finished loading. World.spawn is immediate.
Level.clear removes the level from the game world.
.Level scripting
Each level has a script which is ran when the level is loaded, or the
game is played. A single object declared in it called "scene" can be
used to expose itself to the rest of the game.

65
docs/game.adoc Normal file
View file

@ -0,0 +1,65 @@
= Yugine Engine
The yugine essentially is made of a sequence of levels. Levels can be
nested, they can be streamed in, or loaded one at a time. Levels are
made of levels.
Different "modes" of using the engine has unique sequences of level
loading orders. Each level has an associated script file. Level
loading functions call the script file, as well as the level file. The
level file can be considered to be a container of objects that the
associated script file can access.
.Game start
* Engine scripts
* config.js
* game.lvl & game.js
.Editor
* Engine scripts
* config.js
* editor.js
.Editor play
* F5 debug.lvl
- Used for custom debug level testing. If doesn't exist, game.lvl is loaded.
* F6 game.lvl
* F7 Currently edited level
While playing ...
* F7 Stop
.Scripting
Levels and objects have certain functions you can use that will be
called at particular times during the course of the game running.
setup
Called once, when the object is created.
start
Called once when the gameplay is simulating.
update(dt)
Called once per frame, while the game is simulating
physupdate(dt)
Called once per physics step
stop
Called when the object is destroyed, either by being killed or otherwise.
.Collider functions
Colliders get special functions to help with collision handling.
collide(hit)
Called when an object collides with the object this function is on.
"hit" object
normal - A vector in the direction the hit happened
hit - Object ID of colliding object
sensor - True if the colliding object is a sensor
velocity - A vector of the velocity of the collision

10
docs/video.adoc Normal file
View file

@ -0,0 +1,10 @@
.Yugine video playing
Yugine plays the open source MPEG-TS muxer, using MPEG1 video and MP2 audio.
MPEG-1 video works best at about 1.5 Mbit/s, in SD [720x480] video.
For HD [1920x1080] video, use 9 Mbit/s.
ffmpeg -i "input.video" -vcodec mpeg1video -b:v 1.5M -s 720x480 -acodec mp2 "out.ts"
ffmpeg -i "input.video" -vcodec mpeg1video -b:v 9M -s 1920x1080 -acodec mp2 "out.ts"

View file

@ -553,7 +553,8 @@ JSValue dukext2paths(char *ext)
JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
{ {
int cmd = js2int(argv[0]); int cmd = js2int(argv[0]);
const char *str; const char *str = NULL;
const char *str2 = NULL;
JSValue ret = JS_NULL; JSValue ret = JS_NULL;
switch(cmd) { switch(cmd) {
@ -611,16 +612,23 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
return ret; return ret;
case 12: case 12:
sprite_loadtex(id2sprite(js2int(argv[1])), JS_ToCString(js, argv[2]), js2glrect(argv[3])); str = JS_ToCString(js,argv[2]);
sprite_loadtex(id2sprite(js2int(argv[1])), str, js2glrect(argv[3]));
JS_FreeCString(js,str);
break; break;
case 13: case 13:
str = JS_ToCString(js,argv[1]);
play_song(JS_ToCString(js, argv[1]), JS_ToCString(js, argv[2])); str2 = JS_ToCString(js,argv[2]);
play_song(str,str2);
JS_FreeCString(js,str);
JS_FreeCString(js,str2);
break; break;
case 14: case 14:
mini_sound(JS_ToCString(js, argv[1])); str = JS_ToCString(js, argv[1]);
mini_sound(str);
JS_FreeCString(js,str);
break; break;
case 15: case 15:
@ -712,10 +720,18 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
break; break;
case 38: case 38:
return JS_NewString(js, slurp_text(JS_ToCString(js, argv[1]))); str = JS_ToCString(js,argv[1]);
ret = JS_NewString(js, slurp_text(str));
JS_FreeCString(js,str);
return ret;
case 39: case 39:
return JS_NewInt64(js, slurp_write(JS_ToCString(js, argv[1]), JS_ToCString(js, argv[2]))); str = JS_ToCString(js,argv[1]);
str2 = JS_ToCString(js,argv[2]);
ret = JS_NewInt64(js, slurp_write(str, str2));
JS_FreeCString(js,str);
JS_FreeCString(js,str2);
return ret;
case 40: case 40:
id2go(js2int(argv[1]))->filter.categories = js2bitmask(argv[2]); id2go(js2int(argv[1]))->filter.categories = js2bitmask(argv[2]);
@ -805,13 +821,19 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
return JS_NewFloat64(js, deltaT); return JS_NewFloat64(js, deltaT);
case 64: case 64:
return vec2js(tex_get_dimensions(texture_pullfromfile(JS_ToCString(js, argv[1])))); str = JS_ToCString(js,argv[1]);
ret = vec2js(tex_get_dimensions(texture_pullfromfile(str)));
break;
case 65: case 65:
return JS_NewBool(js, file_exists(JS_ToCString(js, argv[1]))); str = JS_ToCString(js,argv[1]);
ret = JS_NewBool(js, file_exists(str));
break;
case 66: case 66:
return dukext2paths(JS_ToCString(js, argv[1])); str = JS_ToCString(js,argv[1]);
ret = dukext2paths(str);
break;
case 67: case 67:
opengl_rendermode(LIT); opengl_rendermode(LIT);
@ -885,8 +907,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
return ints2js(phys2d_query_box_points(js2vec2(argv[1]), js2vec2(argv[2]), js2cpvec2arr(argv[3]), js2int(argv[4]))); return ints2js(phys2d_query_box_points(js2vec2(argv[1]), js2vec2(argv[2]), js2cpvec2arr(argv[3]), js2int(argv[4])));
case 87: case 87:
mini_music_play(JS_ToCString(js, argv[1])); str = JS_ToCString(js, argv[1]);
return JS_NULL; mini_music_play(str);
break;
case 88: case 88:
mini_music_pause(); mini_music_pause();
@ -897,10 +920,20 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
return JS_NULL; return JS_NULL;
case 90: case 90:
window_set_icon(JS_ToCString(js, argv[1])); str = JS_ToCString(js, argv[1]);
window_set_icon(str);
break; break;
} }
if (str)
JS_FreeCString(js,str);
if (str2)
JS_FreeCString(js,str2);
if (!JS_IsNull(ret))
return ret;
return JS_NULL; return JS_NULL;
} }

View file

@ -38,7 +38,6 @@ void script_init() {
/* Load all prefabs into memory */ /* Load all prefabs into memory */
script_dofile("scripts/engine.js"); script_dofile("scripts/engine.js");
script_dofile("config.js"); script_dofile("config.js");
//ftw(".", load_prefab, 10);
} }
void script_run(const char *script) { void script_run(const char *script) {

View file

@ -122,7 +122,7 @@ struct Texture *texture_loadfromfile(const char *path)
{ {
struct Texture *new = texture_pullfromfile(path); struct Texture *new = texture_pullfromfile(path);
if (new == NULL) { new = texture_pullfromfile("./ph.png"); } if (new == NULL) { new = texture_pullfromfile("./icons/no_tex.png"); }
if (new->id == 0) { if (new->id == 0) {
glGenTextures(1, &new->id); glGenTextures(1, &new->id);

View file

@ -187,7 +187,7 @@ int main(int argc, char **args) {
renderMS = 1.0/vidmode->refreshRate; renderMS = 1.0/vidmode->refreshRate;
if (ed) if (ed)
script_dofile("editor.js"); script_dofile("scripts/editor.js");
else else
script_dofile("game.js"); script_dofile("game.js");

609
source/scripts/base.js Normal file
View file

@ -0,0 +1,609 @@
/* Prototypes out an object and extends with values */
function clone(proto, binds) {
var c = Object.create(proto);
complete_assign(c, binds);
return c;
};
/* Prototypes out an object and assigns values */
function copy(proto, binds) {
var c = Object.create(proto);
Object.assign(c, binds);
return c;
};
/* OBJECT DEFININTIONS */
Object.defineProperty(Object.prototype, 'getOwnPropertyDescriptors', {
value: function() {
var obj = {};
for (var key in this) {
obj[key] = Object.getOwnPropertyDescriptor(this, key);
}
return obj;
}
});
Object.defineProperty(Object.prototype, 'hasOwn', {
value: function(x) { return this.hasOwnProperty(x); }
});
Object.defineProperty(Object.prototype, 'array', {
value: function() {
var a = [];
for (var key in this)
a.push(this[key]);
return a;
},
});
Object.defineProperty(Object.prototype, 'defn', {
value: function(name, val) {
Object.defineProperty(this, name, { value:val, writable:true, configurable:true });
}
});
Object.defineProperty(Object.prototype, 'nulldef', {
value: function(name, val) {
if (!this.hasOwnProperty(name)) this[name] = val;
}
});
/* defc 'define constant'. Defines a value that is not writable. */
Object.defineProperty(Object.prototype, 'defc', {
value: function(name, val) {
Object.defineProperty(this,name, {
value: val,
writable:false,
enumerable:true,
configurable:false,
});
}
});
Object.defineProperty(Object.prototype, 'deflock', {
value: function(prop) {
Object.defineProperty(this,prop, {configurable:false});
}
});
Object.defineProperty(Object.prototype, 'forEach', {
value: function(fn) {
for (var key in this)
fn(this[key]);
}
});
Object.defineProperty(Object.prototype, 'empty', {
get: function() {
return Object.keys(this).empty;
},
});
Object.defineProperty(Object.prototype, 'nth', {
value: function(x) {
if (this.empty || x >= Object.keys(this).length) return null;
return this[Object.keys(this)[x]];
},
});
Object.defineProperty(Object.prototype, 'findIndex', {
value: function(x) {
var i = 0;
for (var key in this) {
if (this[key] === x) return i;
i++;
}
return -1;
}
});
/* STRING DEFS */
Object.defineProperty(String.prototype, 'next', {
value: function(char, from) {
if (!Array.isArray(char))
char = [char];
if (from > this.length-1)
return -1;
else if (!from)
from = 0;
var find = this.slice(from).search(char[0]);
if (find === -1)
return -1;
else
return from + find;
var i = 0;
var c = this.charAt(from+i);
while (!char.includes(c)) {
i++;
if (from+i >this.length-1) return -1;
c = this.charAt(from+i);
}
return from+i;
}
});
Object.defineProperty(String.prototype, 'prev', {
value: function(char, from, count) {
if (from > this.length-1)
return -1;
else if (!from)
from = this.length-1;
if (!count) count = 0;
var find = this.slice(0,from).lastIndexOf(char);
while (count > 1) {
find = this.slice(0,find).lastIndexOf(char);
count--;
}
if (find === -1)
return 0;
else
return find;
}
});
Object.defineProperty(String.prototype, 'shift', {
value: function(n) {
if (n === 0) return this.slice();
if (n > 0)
return this.slice(n);
if (n < 0)
return this.slice(0, this.length+n);
}
});
/* ARRAY DEFS */
Object.defineProperty(Array.prototype, 'copy', {
value: function() {
var c = [];
this.forEach(function(x, i) {
c[i] = deep_copy(x);
});
return c;
}
});
Object.defineProperty(Array.prototype, 'rotate', {
value: function(a) {
return Vector.rotate(this, a);
}
});
Object.defineProperty(Array.prototype, '$add', {
value: function(b) {
for (var i = 0; i < this.length; i++) {
this[i] += b[i];
}
}});
function setelem(n) {
return {
get: function() { return this[n]; },
set: function(x) { this[n] = x; }
}
};
function arrsetelem(str, n)
{
Object.defineProperty(Array.prototype, str, setelem(n));
}
Object.defineProperty(Array.prototype, 'x', setelem(0));
Object.defineProperty(Array.prototype, 'y', setelem(1));
Object.defineProperty(Array.prototype, 'z', setelem(2));
Object.defineProperty(Array.prototype, 'w', setelem(3));
arrsetelem('r', 0);
arrsetelem('g', 1);
arrsetelem('b', 2);
arrsetelem('a', 3);
Object.defineProperty(Array.prototype, 'add', {
value: function(b) {
var c = [];
for (var i = 0; i < this.length; i++) { c[i] = this[i] + b[i]; }
return c;
}});
Object.defineProperty(Array.prototype, 'newfirst', {
value: function(i) {
var c = this.slice();
if (i >= c.length) return c;
do {
c.push(c.shift());
i--;
} while (i > 0);
return c;
}
});
Object.defineProperty(Array.prototype, 'doubleup', {
value: function(n) {
var c = [];
this.forEach(function(x) {
for (var i = 0; i < n; i++)
c.push(x);
});
return c;
}
});
Object.defineProperty(Array.prototype, 'sub', {
value: function(b) {
var c = [];
for (var i = 0; i < this.length; i++) { c[i] = this[i] - b[i]; }
return c;
}});
Object.defineProperty(Array.prototype, 'apply', {
value: function(fn) {
this.forEach(function(x) { x[fn].apply(x); });
}
});
Object.defineProperty(Array.prototype, 'scale', {
value: function(s) {
return this.map(function(x) { return x*s; });
}});
Object.defineProperty(Array.prototype, 'equal', {
value: function(b) {
if (this.length !== b.length) return false;
if (b == null) return false;
if (this === b) return true;
return JSON.stringify(this) === JSON.stringify(b);
for (var i = 0; i < this.length; i++) {
if (!this[i] === b[i])
return false;
}
return true;
}});
function add(x,y) { return x+y; };
function mult(x,y) { return x*y; };
Object.defineProperty(Array.prototype, 'mapc', {
value: function(fn, arr) {
return this.map(function(x, i) {
return fn(x, arr[i]);
});
}
});
Object.defineProperty(Array.prototype, 'remove', {
value: function(b) {
var idx = this.indexOf(b);
if (idx === -1) return false;
this.splice(idx, 1);
return true;
}});
Object.defineProperty(Array.prototype, 'set', {
value: function(b) {
if (this.length !== b.length) return;
b.forEach(function(val, i) { this[i] = val; }, this);
}
});
Object.defineProperty(Array.prototype, 'flat', {
value: function() {
return [].concat.apply([],this);
}
});
Object.defineProperty(Array.prototype, 'any', {
value: function(fn) {
var ev = this.every(function(x) {
return !fn(x);
});
return !ev;
}
});
/* Return true if array contains x */
/*Object.defineProperty(Array.prototype, 'includes', {
value: function(x) {
return this.some(e => e === x);
}});
*/
Object.defineProperty(Array.prototype, 'empty', {
get: function() { return this.length === 0; },
});
Object.defineProperty(Array.prototype, 'push_unique', {
value: function(x) {
if (!this.includes(x)) this.push(x);
}});
Object.defineProperty(Array.prototype, 'unique', {
value: function() {
var c = [];
this.forEach(function(x) { c.push_unique(x); });
return c;
}
});
Object.defineProperty(Array.prototype, 'findIndex', {
value: function(fn) {
var idx = -1;
this.every(function(x, i) {
if (fn(x)) {
idx = i;
return false;
}
return true;
});
return idx;
}});
Object.defineProperty(Array.prototype, 'find', {
value: function(fn) {
var ret;
this.every(function(x) {
if (fn(x)) {
ret = x;
return false;
}
return true;
});
return ret;
}});
Object.defineProperty(Array.prototype, 'last', {
get: function() { return this[this.length-1]; },
});
Object.defineProperty(Array.prototype, 'at', {
value: function(x) {
return x < 0 ? this[this.length+x] : this[x];
}});
Object.defineProperty(Array.prototype, 'wrapped', {
value: function(x) {
var c = this.slice(0, this.length);
for (var i = 0; i < x; i++)
c.push(this[i]);
return c;
}});
Object.defineProperty(Array.prototype, 'wrap_idx', {
value: function(x) {
while (x >= this.length) {
x -= this.length;
}
return x;
}
});
Object.defineProperty(Array.prototype, 'mirrored', {
value: function(x) {
var c = this.slice(0);
if (c.length <= 1) return c;
for (var i = c.length-2; i >= 0; i--)
c.push(c[i]);
return c;
}
});
/* MATH EXTENSIONS */
Math.clamp = function (x, l, h) { return x > h ? h : x < l ? l : x; }
Math.lerp = function (s, f, dt) {
return s + (Math.clamp(dt, 0, 1) * (f - s));
};
Math.snap = function(val, grid) {
if (!grid || grid === 1) return Math.round(val);
var rem = val%grid;
var d = val - rem;
var i = Math.round(rem/grid)*grid;
return d+i;
}
Math.angledist = function (a1, a2) {
var dist = a2 - a1;
var wrap = dist >= 0 ? dist+360 : dist-360;
wrap %= 360;
if (Math.abs(dist) < Math.abs(wrap))
return dist;
return wrap;
};
Math.deg2rad = function(deg) { return deg * 0.0174533; }
Math.rad2deg = function(rad) { return rad / 0.0174533; }
/* BOUNDINGBOXES */
function cwh2bb(c, wh) {
return {
t: c.y+wh.y/2,
b: c.y-wh.y/2,
l: c.x-wh.x/2,
r: c.x+wh.x/2
};
};
function points2bb(points) {
var b= {t:0,b:0,l:0,r:0};
points.forEach(function(x) {
if (x.y > b.t) b.t = x.y;
if (x.y < b.b) b.b = x.y;
if (x.x > b.r) b.r = x.x;
if (x.x < b.l) b.l = x.x;
});
return b;
};
function bb2cwh(bb) {
if (!bb) return undefined;
var cwh = {};
var w = bb.r - bb.l;
var h = bb.t - bb.b;
cwh.wh = [w, h];
cwh.c = [bb.l + w/2, bb.b + h/2];
return cwh;
};
function bb_expand(oldbb, x) {
if (!oldbb || !x) return;
var bb = {};
Object.assign(bb, oldbb);
if (bb.t < x.t) bb.t = x.t;
if (bb.r < x.r) bb.r = x.r;
if (bb.b > x.b) bb.b = x.b;
if (bb.l > x.l) bb.l = x.l;
return bb;
};
function bb_draw(bb, color) {
if (!bb) return;
var draw = bb2cwh(bb);
draw.wh[0] /= editor.camera.zoom;
draw.wh[1] /= editor.camera.zoom;
Debug.box(world2screen(draw.c), draw.wh, color);
};
function bb_from_objects(objs) {
var bb = objs[0].boundingbox;
objs.forEach(function(obj) { bb = bb_expand(bb, obj.boundingbox); });
return bb;
};
/* VECTORS */
var Vector = {
x: 0,
y: 0,
length(v) {
var sum = v.reduce(function(acc, val) { return acc + val**2; }, 0);
return Math.sqrt(sum);
},
norm(v) {
var len = Vector.length(v);
return [v.x/len, v.y/len];
},
make(x, y) {
var vec = Object.create(this, {x:x, y:y});
},
project(a, b) {
return cmd(85, a, b);
},
dot(a, b) {
},
random() {
var vec = [Math.random()-0.5, Math.random()-0.5];
return Vector.norm(vec);
},
angle(v) {
return Math.atan2(v.y, v.x);
},
rotate(v,angle) {
var r = Vector.length(v);
var p = Vector.angle(v) + angle;
return [r*Math.cos(p), r*Math.sin(p)];
},
equal(v1, v2, tol) {
if (!tol)
return v1.equal(v2);
var eql = true;
var c = v1.sub(v2);
c.forEach(function(x) {
if (!eql) return;
if (Math.abs(x) > tol)
eql = false;
});
return eql;
},
};
/* POINT ASSISTANCE */
function points2cm(points)
{
var x = 0;
var y = 0;
var n = points.length;
points.forEach(function(p) {
x = x + p[0];
y = y + p[1];
});
return [x/n,y/n];
};
function sortpointsccw(points)
{
var cm = points2cm(points);
var cmpoints = points.map(function(x) { return x.sub(cm); });
var ccw = cmpoints.sort(function(a,b) {
aatan = Math.atan2(a.y, a.x);
batan = Math.atan2(b.y, b.x);
return aatan - batan;
});
return ccw.map(function(x) { return x.add(cm); });
}

View file

@ -0,0 +1,657 @@
var component = {
toString() {
if ('gameobject' in this)
return this.name + " on " + this.gameobject;
else
return this.name;
},
name: "component",
component: true,
set enabled(x) { },
get enabled() { },
enable() { this.enabled = true; },
disable() { this.enabled = false; },
make(go) { },
kill() { Log.info("Kill not created for this component yet"); },
gui() { },
gizmo() { },
prepare_center() {},
finish_center() {},
clone(spec) {
return clone(this, spec);
},
};
var sprite = clone(component, {
name: "sprite",
path: "",
_pos: [0, 0],
get layer() {
if (!this.gameobject)
return 0;
else
return this.gameobject.draw_layer;
},
get pos() { return this._pos; },
set pos(x) {
this._pos = x;
this.sync();
},
get boundingbox() {
if (!this.gameobject) return null;
var dim = cmd(64, this.path);
dim = dim.scale(this.gameobject.scale);
var realpos = this.pos.copy();
realpos.x *= dim.x;
realpos.y *= dim.y;
realpos.x += (dim.x/2);
realpos.y += (dim.y/2);
return cwh2bb(realpos, dim);
},
set asset(x) {
if (!x) return;
if (!x.endsWith(".png")) {
Log.error("Can't set texture to a non image.");
return;
}
this.path = x;
Log.info("path is now " + x);
this.sync();
},
_enabled: true,
set enabled(x) { this._enabled = x; cmd(20, this.id, x); },
get enabled() { return this._enabled; return cmd(21, this.id); },
get visible() { return this.enabled; },
set visible(x) { this.enabled = x; },
_angle: 0,
get angle() { return this._angle; },
set angle(x) {
this._angle = x;
sync();
},
make(go) {
var sprite = clone(this);
Object.defineProperty(sprite, 'id', {value:make_sprite(go,this.path,this.pos)});
sprite.sync();
return sprite;
},
rect: {s0:0, s1: 1, t0: 0, t1: 1},
sync() {
if (!this.hasOwn('id')) return;
cmd(60, this.id, this.layer);
cmd(37, this.id, this.pos);
cmd(12, this.id, this.path, this.rect);
},
load_img(img) {
cmd(12, this.id, img);
},
kill() {
cmd(9, this.id);
},
});
/* Container to play sprites and anim2ds */
var char2d = clone(sprite, {
clone(anims) {
var char = clone(this);
char.anims = anims;
return char;
},
name: "char 2d",
frame2rect(frames, frame) {
var rect = {s0:0,s1:1,t0:0,t1:1};
var frameslice = 1/frames;
rect.s0 = frameslice*frame;
rect.s1 = frameslice*(frame+1);
return rect;
},
make(go) {
var char = clone(this);
char.curplaying = char.anims.array()[0];
Object.defineProperty(char, 'id', {value:make_sprite(go,this.path,this.pos)});
char.frame = 0;
char.timer = timer.make(char.advance, 1/char.curplaying.fps, char);
char.timer.loop = true;
char.rect = char.frame2rect(char.curplaying.frames, char.frame);
char.setsprite();
return char;
},
frame: 0,
play(name) {
if (!(name in this.anims)) {
Log.info("Can't find an animation named " + name);
return;
}
if (this.curplaying === this.anims[name]) return;
this.curplaying = this.anims[name];
this.timer.time = 1/this.curplaying.fps;
this.timer.start();
this.frame = 0;
this.setsprite();
},
setsprite() {
this.path = this.curplaying.path;
this.rect = this.frame2rect(this.curplaying.frames, this.frame);
cmd(12, this.id, this.path, this.rect);
},
advance() {
this.frame = (this.frame + 1) % this.curplaying.frames;
this.setsprite();
},
devance() {
this.frame = (this.frame - 1);
if (this.frame === -1) this.frame = this.curplaying.frames-1;
this.setsprite();
},
pause() {
this.timer.pause();
},
stop() {
this.frame = 0;
this.timer.stop();
this.setsprite();
},
kill() {
this.timer.kill();
cmd(9, this.id);
},
});
/* For all colliders, "shape" is a pointer to a phys2d_shape, "id" is a pointer to the shape data */
var collider2d = clone(component, {
name: "collider 2d",
_sensor: false,
set sensor(x) {
this._sensor = x;
if (this.shape)
cmd(18, this.shape, x);
},
get sensor() { return this._sensor; },
input_s_pressed() {
if (!Keys.alt()) return;
this.sensor = !this.sensor;
},
input_t_pressed() {
if (!Keys.alt()) return;
this.enabled = !this.enabled;
},
coll_sync() {
cmd(18, this.shape, this.sensor);
},
_enabled: true,
set enabled(x) {this._enabled = x; if (this.id) cmd(22, this.id, x); },
get enabled() { return this._enabled; },
kill() {}, /* No killing is necessary - it is done through the gameobject's kill */
register_hit(fn, obj) {
register_collide(1, fn, obj, this.gameobject.body, this.shape);
},
});
var polygon2d = clone(collider2d, {
name: "polygon 2d",
points: [],
help: "Ctrl-click Add a point\nShift-click Remove a point",
clone(spec) {
var obj = Object.create(this);
obj.points = this.points.copy();
Object.assign(obj, spec);
return obj;
},
make(go) {
var poly = Object.create(this);
Object.assign(poly, make_poly2d(go, this.points));
Object.defineProperty(poly, 'id', {enumerable:false});
Object.defineProperty(poly, 'shape', {enumerable:false});
return poly;
},
get boundingbox() {
if (!this.gameobject) return null;
var scaledpoints = [];
this.points.forEach(function(x) { scaledpoints.push(x.scale(this.gameobject.scale)); }, this);
return points2bb(scaledpoints);
},
input_f10_pressed() {
this.points = sortpointsccw(this.points);
},
sync() {
if (!this.id) return;
cmd_poly2d(0, this.id, this.spoints);
this.coll_sync();
},
input_b_pressed() {
if (!Keys.ctrl()) return;
this.points = this.spoints;
this.mirrorx = false;
this.mirrory = false;
},
get spoints() {
var spoints = this.points.slice();
if (this.mirrorx) {
spoints.forEach(function(x) {
var newpoint = x.slice();
newpoint.x = -newpoint.x;
spoints.push(newpoint);
});
}
if (this.mirrory) {
spoints.forEach(function(x) {
var newpoint = x.slice();
newpoint.y = -newpoint.y;
spoints.push(newpoint);
});
}
return spoints;
},
gizmo() {
if (!this.hasOwn('points')) this.points = this.__proto__.points.copy();
this.spoints.forEach(function(x) {
Debug.point(world2screen(this.gameobject.this2world(x)), 3, Color.green);
}, this);
this.points.forEach(function(x, i) {
Debug.numbered_point(this.gameobject.this2world(x), i);
}, this);
this.sync();
},
input_lmouse_pressed() {
if (Keys.ctrl()) {
this.points.push(this.gameobject.world2this(Mouse.worldpos));
} else if (Keys.shift()) {
var idx = grab_from_points(screen2world(Mouse.pos), this.points.map(this.gameobject.this2world,this.gameobject), 25);
if (idx === -1) return;
this.points.splice(idx, 1);
}
},
pick(pos) {
return Gizmos.pick_gameobject_points(pos, this.gameobject, this.points);
},
query() {
return cmd(80, this.shape);
},
mirrorx: false,
mirrory: false,
input_m_pressed() {
if (Keys.ctrl())
this.mirrory = !this.mirrory;
else
this.mirrorx = !this.mirrorx;
},
});
var bucket = clone(collider2d, {
name: "bucket",
help: "Ctrl-click Add a point\nShift-click Remove a point\n+,- Increase/decrease spline segs\nCtrl-+,- Inc/dec spline degrees\nCtrl-b,v Inc/dec spline thickness",
clone(spec) {
var obj = Object.create(this);
obj.cpoints = this.cpoints.copy();
dainty_assign(obj, spec);
return obj;
},
cpoints:[],
degrees:2,
dimensions:2,
/* open: 0
clamped: 1
beziers: 2
looped: 3
*/
type: 3,
get boundingbox() {
if (!this.gameobject) return null;
var scaledpoints = [];
this.points.forEach(function(x) { scaledpoints.push(x.scale(this.gameobject.scale)); }, this);
return points2bb(scaledpoints);
},
mirrorx: false,
mirrory: false,
input_m_pressed() {
if (Keys.ctrl()) {
this.mirrory = !this.mirrory;
} else {
this.mirrorx = !this.mirrorx;
}
this.sync();
},
hollow: false,
input_h_pressed() {
this.hollow = !this.hollow;
},
get spoints() {
var spoints = this.cpoints.slice();
if (this.mirrorx) {
for (var i = spoints.length-1; i >= 0; i--) {
var newpoint = spoints[i].slice();
newpoint.x = -newpoint.x;
spoints.push(newpoint);
}
}
if (this.mirrory) {
for (var i = spoints.length-1; i >= 0; i--) {
var newpoint = spoints[i].slice();
newpoint.y = -newpoint.y;
spoints.push(newpoint);
}
}
return spoints;
if (this.hollow) {
var hpoints = [];
var inflatep = inflate_cpv(spoints, spoints.length, this.hollowt);
inflatep[0].slice().reverse().forEach(function(x) { hpoints.push(x); });
inflatep[1].forEach(function(x) { hpoints.push(x); });
return hpoints;
}
return spoints;
},
hollowt: 0,
input_g_pressed() {
if (!Keys.ctrl()) return;
this.hollowt--;
if (this.hollowt < 0) this.hollowt = 0;
},
input_f_pressed() {
if (!Keys.ctrl()) return;
this.hollowt++;
},
sample(n) {
var spoints = this.spoints;
this.degrees = Math.clamp(this.degrees, 1, spoints.length-1);
if (spoints.length === 2)
return spoints;
if (spoints.length < 2)
return [];
if (this.degrees < 2) {
if (this.type === 3)
return spoints.wrapped(1);
return spoints;
}
/*
order = degrees+1
knots = spoints.length + order
assert knots%order != 0
*/
if (this.type === 3)
return spline_cmd(0, this.degrees, this.dimensions, 0, spoints.wrapped(this.degrees), n);
return spline_cmd(0, this.degrees, this.dimensions, this.type, spoints, n);
},
samples: 10,
points:[],
make(go) {
var edge = Object.create(this);
Object.assign(edge, make_edge2d(go, this.points, this.thickness));
Object.defineProperty(edge, 'id', {enumerable:false});
Object.defineProperty(edge, 'shape', {enumerable:false});
edge.defn('points', []);
// Object.defineProperty(edge, 'points', {enumerable:false});
edge.sync();
return edge;
},
sync() {
if (!this.gameobject) return;
this.points = this.sample(this.samples);
cmd_edge2d(0, this.id, this.points);
cmd_edge2d(1, this.id, this._thickness * this.gameobject.scale);
this.coll_sync();
},
gizmo() {
if (!this.hasOwn('cpoints')) this.cpoints = this.__proto__.cpoints.copy();
this.spoints.forEach(function(x) {
Debug.point(world2screen(this.gameobject.this2world(x)), 3, Color.green);
}, this);
this.cpoints.forEach(function(x, i) {
Debug.numbered_point(this.gameobject.this2world(x), i);
}, this);
this.sync();
},
_thickness:0, /* Number of pixels out the edge is */
get thickness() { return this._thickness; },
set thickness(x) {
this._thickness = Math.max(x, 0);
cmd_edge2d(1, this.id, this._thickness);
},
input_v_pressrep() {
if (!Keys.alt()) return;
this.thickness--;
},
input_b_pressrep() {
if (Keys.alt()) {
this.thickness++;
} else if (Keys.ctrl()) {
this.cpoints = this.spoints;
this.mirrorx = false;
this.mirrory = false;
}
},
finish_center(change) {
this.cpoints = this.cpoints.map(function(x) { return x.sub(change); });
this.sync();
},
input_plus_pressrep() {
if (Keys.ctrl())
this.degrees++;
else
this.samples += 1;
},
input_minus_pressrep() {
if (Keys.ctrl())
this.degrees--;
else {
this.samples -= 1;
if (this.samples < 1) this.samples = 1;
}
},
input_r_pressed() {
if (!Keys.ctrl()) return;
this.cpoints = this.cpoints.reverse();
},
input_l_pressed() {
if (!Keys.ctrl()) return;
this.type = 3;
},
input_c_pressed() {
if (!Keys.ctrl()) return;
this.type = 1;
},
input_o_pressed() {
if (!Keys.ctrl()) return;
this.type = 0;
},
input_lmouse_pressed() {
if (Keys.ctrl()) {
if (Keys.alt()) {
var idx = grab_from_points(Mouse.worldpos, this.cpoints.map(this.gameobject.world2this,this.gameobject), 25);
if (idx === -1) return;
this.cpoints = this.cpoints.newfirst(idx);
return;
}
var idx = 0;
if (this.cpoints.length >= 2) {
idx = cmd(59, screen2world(Mouse.pos).sub(this.gameobject.pos), this.cpoints, 1000);
if (idx === -1) return;
}
if (idx === this.cpoints.length)
this.cpoints.push(this.gameobject.world2this(screen2world(Mouse.pos)));
else
this.cpoints.splice(idx, 0, this.gameobject.world2this(screen2world(Mouse.pos)));
return;
} else if (Keys.shift()) {
var idx = grab_from_points(screen2world(Mouse.pos), this.cpoints.map(function(x) {return x.add(this.gameobject.pos); }, this), 25);
if (idx === -1) return;
this.cpoints.splice(idx, 1);
}
},
pick(pos) { return Gizmos.pick_gameobject_points(pos, this.gameobject, this.cpoints); },
input_lbracket_pressrep() {
var np = [];
this.cpoints.forEach(function(c) {
np.push(Vector.rotate(c, Math.deg2rad(-1)));
});
this.cpoints = np;
},
input_rbracket_pressrep() {
var np = [];
this.cpoints.forEach(function(c) {
np.push(Vector.rotate(c, Math.deg2rad(1)));
});
this.cpoints = np;
},
});
var circle2d = clone(collider2d, {
name: "circle 2d",
get radius() {
return this.rradius;
},
rradius: 10,
set radius(x) {
this.rradius = x;
cmd_circle2d(0, this.id, this.rradius);
},
get boundingbox() {
if (!this.gameobject) return null;
var radius = this.radius*2*this.gameobject.scale;
return cwh2bb(this.offset.scale(this.gameobject.scale), [radius, radius]);
},
get scale() { return this.radius; },
set scale(x) { this.radius = x; },
ofset: [0,0],
get offset() { return this.ofset; },
set offset(x) { this.ofset = x; cmd_circle2d(1, this.id, this.ofset); },
get pos() { return this.ofset; },
set pos(x) { this.offset = x; },
make(go) {
var circle = clone(this);
var circ = make_circle2d(go, circle.radius, circle.offset);
Object.assign(circle, circ);
Object.defineProperty(circle, 'id', {enumerable:false});
Object.defineProperty(circle, 'shape', {enumerable:false});
return circle;
},
gui() {
Nuke.newline();
Nuke.label("circle2d");
this.radius = Nuke.pprop("Radius", this.radius);
this.offset = Nuke.pprop("offset", this.offset);
},
sync() {
cmd_circle2d(0, this.id, this.rradius);
cmd_circle2d(-1, this.id);
this.coll_sync();
},
});

207
source/scripts/debug.js Normal file
View file

@ -0,0 +1,207 @@
var Gizmos = {
pick_gameobject_points(worldpos, gameobject, points) {
var idx = grab_from_points(worldpos, points.map(gameobject.this2world,gameobject), 25);
if (idx === -1) return null;
return points[idx];
},
};
var Debug = {
draw_grid(width, span) {
cmd(47, width, span);
},
point(pos, size, color) {
color = color ? color : Color.blue;
cmd(51, pos, size,color);
},
arrow(start, end, color, capsize) {
color = color ? color : Color.red;
if (!capsize)
capsize = 4;
cmd(81, start, end, color, capsize);
},
box(pos, wh, color) {
color = color ? color : Color.white;
cmd(53, pos, wh, color);
},
numbered_point(pos, n) {
Debug.point(world2screen(pos), 3);
gui_text(n, world2screen(pos).add([0,4]), 1);
},
phys_drawing: false,
draw_phys(on) {
this.phys_drawing = on;
cmd(4, this.phys_drawing);
},
draw_obj_phys(obj) {
cmd(82, obj.body);
},
register_call(fn, obj) {
register_debug(fn,obj);
},
print_callstack() {
for (var i = -3;; i--) {
var t = Duktape.act(i);
if (!t) break;
var file = t.function ? t.function.fileName : "";
var line = t.lineNumber;
Log.info(file + ":" + line);
}
},
line(points, color, type) {
if (!type)
type = 0;
if (!color)
color = Color.white;
switch (type) {
case 0:
cmd(83, points, color);
}
},
};
var Gizmos = {
pick_gameobject_points(worldpos, gameobject, points) {
var idx = grab_from_points(worldpos, points.map(gameobject.this2world,gameobject), 25);
if (idx === -1) return null;
return points[idx];
},
};
var Nuke = {
newline(cols) { nuke(3, cols ? cols : 1); },
newrow(height) { nuke(13,height); },
wins: {},
curwin:"",
prop(str, v) {
var ret = nuke(2, str, v);
if (Number.isFinite(ret)) return ret;
return 0;
},
treeid: 0,
tree(str) { var on = nuke(11, str, this.treeid); this.treeid++; return on; },
tree_pop() { nuke(12);},
prop_num(str, num) { return nuke(2, str, num, -1e10, 1e10, 0.01); },
prop_bool(str, val) { return nuke(4, str, val); },
checkbox(val) { return nuke(4,"",val); },
label(str) { nuke(5, str); },
textbox(str) { return nuke(7, str); },
scrolltext(str) { nuke(14,str); },
defaultrect: { x:10, y:10, w:400, h:600 },
window(name) {
this.curwin = name;
var rect;
if (name in this.wins)
rect = this.wins[name];
else
rect = { x:10, y:10, w:400, h:600 };
nuke(0, name, rect);
},
button(name) { return nuke(6, name); },
radio(name, val, cmp) { return nuke(9, name, val, cmp); },
img(path) { nuke(8, path); },
end() {
this.wins[this.curwin] = nuke(10);
this.treeid = 0;
nuke(1);
},
pprop(str, p, nonew) {
switch(typeof p) {
case 'number':
if (!nonew) Nuke.newline();
return Nuke.prop_num(str, p);
break;
case 'boolean':
if (!nonew) Nuke.newline();
return Nuke.prop_bool(str, p);
case 'object':
if (Array.isArray(p)) {
var arr = [];
Nuke.newline(p.length+1);
Nuke.label(str);
arr[0] = Nuke.pprop("#x", p[0], true);
arr[1] = Nuke.pprop("#y", p[1], true);
return arr;
} else {
if (!nonew)Nuke.newline(2);
Nuke.label(str);
Nuke.label(p);
}
break;
case 'string':
if (!nonew) Nuke.newline();
Nuke.label(str);
return Nuke.textbox(p);
default:
if (!nonew) Nuke.newline(2);
Nuke.label(str);
Nuke.label(p);
}
},
};
Object.defineProperty(Nuke, "curwin", {enumerable:false});
Object.defineProperty(Nuke, "defaultrect", {enumerable:false});
var Log = {
print(msg, lvl) {
var lg;
if (typeof msg === 'object') {
lg = JSON.stringify(msg, null, 2);
} else {
lg = msg;
}
var stack = (new Error()).stack;
var n = stack.next('\n',0)+1;
n = stack.next('\n', n)+1;
var nnn = stack.slice(n);
var fmatch = nnn.match(/\(.*\:/);
var file = fmatch ? fmatch[0].shift(1).shift(-1) : "nofile";
var lmatch = nnn.match(/\:\d*\)/);
var line = lmatch ? lmatch[0].shift(1).shift(-1) : "0";
yughlog(lvl, lg, file, line);
},
info(msg) {
this.print(msg, 0);
},
warn(msg) {
this.print(msg, 1);
},
error(msg) {
this.print(msg, 2);
},
};

141
source/scripts/diff.js Normal file
View file

@ -0,0 +1,141 @@
function deep_copy(from) {
if (typeof from !== 'object')
return from;
if (Array.isArray(from)) {
var c = [];
from.forEach(function(x,i) { c[i] = deep_copy(x); });
return c;
}
var obj = {};
for (var key in from)
obj[key] = deep_copy(from[key]);
return obj;
};
var walk_up_get_prop = function(obj, prop, endobj) {
var props = [];
var cur = obj;
while (cur !== Object.prototype) {
if (cur.hasOwn(prop))
props.push(cur[prop]);
cur = cur.__proto__;
}
return props;
};
function complete_assign(target, source) {
var descriptors = {};
var assigns = {};
if (typeof source === 'undefined') return target;
Object.keys(source).forEach(function (k) {
var desc = Object.getOwnPropertyDescriptor(source, k);
if (desc.value) {
if (typeof desc.value === 'object' && desc.value.hasOwn('value'))
descriptors[k] = desc.value;
else
assigns[k] = desc.value;
} else
descriptors[k] = desc;
});
Object.defineProperties(target, descriptors);
Object.assign(target, assigns);
return target;
};
/* Assigns properties from source to target, only if they exist in target */
function dainty_assign(target, source)
{
for (var key in source) {
if (typeof source[key] === 'function') {
target[key] = source[key];
continue;
}
if (!(key in target)) continue;
if (Array.isArray(target[key]))
target[key] = source[key];
else if (typeof target[key] === 'object')
dainty_assign(target[key], source[key]);
else
target[key] = source[key];
}
};
/* Deeply remove source keys from target, not removing objects */
function unmerge(target, source) {
for (var key in source) {
if (typeof source[key] === 'object' && !Array.isArray(source[key]))
unmerge(target[key], source[key]);
else
delete target[key];
}
};
/* Deeply merge two objects, not clobbering objects on target with objects on source */
function deep_merge(target, source)
{
for (var key in source) {
if (typeof source[key] === 'object' && !Array.isArray(source[key]))
deep_merge(target[key], source[key]);
else
target[key] = source[key];
}
};
function equal(x,y) {
if (typeof x === 'object')
for (var key in x)
return equal(x[key],y[key]);
return x === y;
};
function diffassign(target, from) {
if (from.empty) return;
for (var e in from) {
if (typeof from[e] === 'object') {
if (!target.hasOwnProperty(e))
target[e] = from[e];
else
diffassign(target[e], from[e]);
} else {
if (from[e] === "DELETE") {
delete target[e];
} else {
target[e] = from[e];
}
}
}
};
function diff(from, to) {
var obj = {};
for (var e in to) {
if (typeof to[e] === 'object' && from.hasOwnProperty(e)) {
obj[e] = diff(from[e], to[e]);
if (obj[e].empty)
delete obj[e];
} else {
if (from[e] !== to[e])
obj[e] = to[e];
}
}
for (var e in from) {
if (!to.hasOwnProperty(e))
obj[e] = "DELETE";
}
return obj;
};

2572
source/scripts/editor.js Normal file

File diff suppressed because it is too large Load diff

1597
source/scripts/engine.js Normal file

File diff suppressed because it is too large Load diff