diff --git a/docs/game.adoc b/docs/game.adoc index c58131f..b98241b 100644 --- a/docs/game.adoc +++ b/docs/game.adoc @@ -50,44 +50,3 @@ be rendered: is it a sprite, is it a texture, does it have mipmaps, etc. Textures are all file based, and are only accessed through the explicit path to their associated image file. -.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 - -.Draw functions -draw() - Called during lower rendering - -gui() - Called for screen space over the top rendering - -debug() - Called during a debug phase; Will not be called when debug draw is off diff --git a/docs/scripting.md b/docs/scripting.md index 05a583e..b90c031 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -1,3 +1,46 @@ # Yugine Scripting -Scripting is done with mruby. MAY CHANGE. \ No newline at end of file +Scripting is done with JS. + +Load up objects with functions that are called back at specific times. + +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 + +.Draw functions +draw() + Called during lower rendering + +gui() + Called for screen space over the top rendering + +debug() + Called during a debug phase; Will not be called when debug draw is off + diff --git a/source/engine/2dphysics.c b/source/engine/2dphysics.c index d040c9e..d9e910b 100644 --- a/source/engine/2dphysics.c +++ b/source/engine/2dphysics.c @@ -164,6 +164,8 @@ void phys2d_init() { space = cpSpaceNew(); cpSpaceSetSleepTimeThreshold(space, 1); + cpSpaceSetCollisionSlop(space, 0.01); + cpSpaceSetCollisionBias(space, cpfpow(1.0-0.5, 165.f)); } void phys2d_set_gravity(cpVect v) { @@ -613,6 +615,7 @@ void duk_call_phys_cb(cpVect norm, struct callee c, int hit, cpArbiter *arb) { JS_SetPropertyStr(js, obj, "sensor", JS_NewBool(js, cpShapeGetSensor(shape2))); JS_SetPropertyStr(js, obj, "velocity", vec2js(cpArbiterGetSurfaceVelocity(arb))); JS_SetPropertyStr(js, obj, "pos", vec2js(cpArbiterGetPointA(arb, 0))); + JS_SetPropertyStr(js,obj,"depth", num2js(cpArbiterGetDepth(arb,0))); JS_SetPropertyStr(js, obj, "id", JS_NewInt32(js,hit)); JS_SetPropertyStr(js,obj,"obj", JS_DupValue(js,id2go(hit)->ref)); diff --git a/source/engine/debug/debugdraw.c b/source/engine/debug/debugdraw.c index 7e3d401..92007c7 100644 --- a/source/engine/debug/debugdraw.c +++ b/source/engine/debug/debugdraw.c @@ -81,7 +81,6 @@ static struct circle_vertex circle_b[v_amt]; void debug_flush() { - if (poly_c != 0) { sg_apply_pipeline(poly_pipe); sg_apply_bindings(&poly_bind); @@ -136,7 +135,6 @@ void debug_flush() sg_draw(0,4,circle_count); circle_count = 0; } - } static sg_shader_uniform_block_desc projection_ubo = { @@ -586,7 +584,7 @@ void draw_poly(cpVect *points, int n, struct rgba color) /* Find polygon mesh */ int tric = n - 2; - if (n < 1) return; + if (tric < 1) return; uint32_t tridxs[tric*3]; diff --git a/source/engine/ffi.c b/source/engine/ffi.c index b91e203..8cc2570 100644 --- a/source/engine/ffi.c +++ b/source/engine/ffi.c @@ -899,7 +899,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) return JS_NULL; case 83: - draw_edge(js2cpvec2arr(argv[1]), 2, js2color(argv[2]), 1, 0, 0, js2color(argv[2]), 10); + draw_edge(js2cpvec2arr(argv[1]), js_arrlen(argv[1]), js2color(argv[2]), js2number(argv[3]), 0, 0, js2color(argv[2]), 10); return JS_NULL; case 84: @@ -1021,6 +1021,14 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 115: draw_circle(js2vec2(argv[1]), js2number(argv[2]), js2number(argv[2]), js2color(argv[3]), -1); break; + + case 116: + return str2js(tex_get_path(js2sprite(argv[1])->tex)); + case 117: + str = JS_ToCString(js, argv[1]); + ret = JS_NewInt64(js, script_runfile(str)); + break; + } if (str) diff --git a/source/engine/script.c b/source/engine/script.c index 633a71b..40fb1fe 100644 --- a/source/engine/script.c +++ b/source/engine/script.c @@ -20,7 +20,7 @@ JSRuntime *rt = NULL; #ifdef DBG #define JS_EVAL_FLAGS JS_EVAL_FLAG_STRICT #else -#define JS_EVAL_FLAGS JS_EVAL_FLAG_STRICT | JS_EVAL_FLAG_STRIP +#define JS_EVAL_FLAGS JS_EVAL_FLAG_STRICT | JS_EVAL_FLAG_STRIP #endif static int load_prefab(const char *fpath, const struct stat *sb, int typeflag) { @@ -53,8 +53,10 @@ void script_init() { num_cache[i] = int2js(i); } -void script_run(const char *script) { - JS_FreeValue(js, JS_Eval(js, script, strlen(script), "script", JS_EVAL_FLAGS)); +void script_run(const char *script, const char *file) { + JSValue obj = JS_Eval(js, script, strlen(script), file, JS_EVAL_FLAGS); + js_print_exception(obj); + JS_FreeValue(js,obj); } void compile_script(const char *file) { @@ -91,20 +93,22 @@ int js_print_exception(JSValue v) { #ifdef DBG if (JS_IsException(v)) { JSValue exception = JS_GetException(js); + /* TODO: Does it need freed if null? */ if (JS_IsNull(exception)) return 0; + JSValue val = JS_GetPropertyStr(js, exception, "stack"); - const char *name = JS_ToCString(js, JS_GetPropertyStr(js, exception, "name")); - const char *msg = JS_ToCString(js, JS_GetPropertyStr(js, exception, "message")); - const char *stack = JS_ToCString(js, val); - YughLog(LOG_SCRIPT, LOG_ERROR, "%s :: %s\n%s", name, msg,stack); + const char *name = JS_ToCString(js, JS_GetPropertyStr(js, exception, "name")); + const char *msg = JS_ToCString(js, JS_GetPropertyStr(js, exception, "message")); + const char *stack = JS_ToCString(js, val); + YughLog(LOG_SCRIPT, LOG_ERROR, "%s :: %s\n%s", name, msg,stack); - JS_FreeCString(js, name); - JS_FreeCString(js, msg); - JS_FreeCString(js, stack); - JS_FreeValue(js,val); - JS_FreeValue(js,exception); + JS_FreeCString(js, name); + JS_FreeCString(js, msg); + JS_FreeCString(js, stack); + JS_FreeValue(js,val); + JS_FreeValue(js,exception); return 1; } @@ -112,17 +116,27 @@ int js_print_exception(JSValue v) { return 0; } + + int script_dofile(const char *file) { - YughInfo("Doing script %s", file); const char *script = slurp_text(file); if (!script) { YughError("Can't find file %s.", file); return 0; } - JSValue obj = JS_Eval(js, script, strlen(script), file, JS_EVAL_FLAGS); - js_print_exception(obj); - JS_FreeValue(js, obj); + script_run(script,file); + free(script); + return file_mod_secs(file); +} +int script_runfile(const char *file) +{ + const char *script = slurp_text(file); + int bufsize = strlen(script)+50; + char scriptbuffer[bufsize]; + snprintf(scriptbuffer,bufsize, "(function(){%s})()", script); + script_run(scriptbuffer,file); + free(script); return file_mod_secs(file); } diff --git a/source/engine/script.h b/source/engine/script.h index 6e18120..291bc77 100644 --- a/source/engine/script.h +++ b/source/engine/script.h @@ -18,8 +18,9 @@ extern JSValue num_cache[100]; void js_stacktrace(); void script_startup(); void script_init(); -void script_run(const char *script); +void script_run(const char *script, const char *file); int script_dofile(const char *file); +int script_runfile(const char *file); void script_update(double dt); void script_draw(); diff --git a/source/engine/sprite.c b/source/engine/sprite.c index e2cc547..b606ee6 100644 --- a/source/engine/sprite.c +++ b/source/engine/sprite.c @@ -19,7 +19,6 @@ static int first = -1; static sg_pipeline pip_sprite; static sg_bindings bind_sprite; -static int sprite_c = 0; int make_sprite(int go) { struct sprite sprite = { diff --git a/source/engine/texture.c b/source/engine/texture.c index 8205020..f4be237 100644 --- a/source/engine/texture.c +++ b/source/engine/texture.c @@ -156,7 +156,7 @@ char *tex_get_path(struct Texture *tex) { } } - return NULL; + return ""; } struct Texture *texture_loadfromfile(const char *path) { diff --git a/source/engine/texture.h b/source/engine/texture.h index 5b5e750..c37a886 100644 --- a/source/engine/texture.h +++ b/source/engine/texture.h @@ -61,8 +61,8 @@ struct TextureOptions { /* Represents an actual texture on the GPU */ struct Texture { sg_image id; /* ID reference for the GPU memory location of the texture */ - int width; - int height; + uint16_t width; + uint16_t height; unsigned char *data; struct TextureOptions opts; struct TexAnim anim; diff --git a/source/engine/timer.c b/source/engine/timer.c index 482307d..5dac751 100644 --- a/source/engine/timer.c +++ b/source/engine/timer.c @@ -73,6 +73,7 @@ void timer_remove(int id) { struct timer *t = id2timer(id); if (t->owndata) free(t->data); t->next = first; + t->on = 0; first = id; } diff --git a/source/engine/yugine.c b/source/engine/yugine.c index 44a8ad1..ec7f005 100644 --- a/source/engine/yugine.c +++ b/source/engine/yugine.c @@ -42,8 +42,8 @@ double physlag = 0; double updatelag = 0; double renderMS = 1 / 165.f; -double physMS = 1 / 240.f; -double updateMS = 1 / 60.f; +double physMS = 1 / 165.f; +double updateMS = 1 / 165.f; static int ed = 1; static int sim_play = 0; @@ -234,14 +234,14 @@ int main(int argc, char **args) { timer_update(elapsed * timescale); physlag += elapsed; call_updates(elapsed * timescale); - while (physlag >= physMS) { +// while (physlag >= physMS) { phys_step = 1; physlag -= physMS; phys2d_update(physMS * timescale); call_physics(physMS * timescale); if (sim_play == SIM_STEP) sim_pause(); phys_step = 0; - } +// } } renderlag += elapsed; diff --git a/source/scripts/components.js b/source/scripts/components.js index 904fd04..ee09d0f 100644 --- a/source/scripts/components.js +++ b/source/scripts/components.js @@ -31,7 +31,43 @@ var sprite = clone(component, { get visible() { return this.enabled; }, set visible(x) { this.enabled = x; }, angle: 0, - rect: {s0:0, s1: 1, t0: 0, t1: 1}, + rect: {s0:0, s1: 1, t0: 0, t1: 1}, + + get dimensions() { return cmd(64,this.path); }, + set dimensions(x) {}, + + make(go) { + var sprite = Object.create(this); + sprite.id = make_sprite(go,this.path,this.pos); + complete_assign(sprite, { + get enabled() { return cmd(114,this.id); }, + set enabled(x) { cmd(20,this.id,x); }, + set color(x) { cmd(96,this.id,x); }, + get pos() { return cmd(111, this.id); }, + set pos(x) { cmd(37,this.id,x); }, + set layer(x) { cmd(60, this.id, x); }, + get layer() { return this.gameobject.draw_layer; }, + + get boundingbox() { + var dim = this.dimensions; + dim = dim.scale(this.gameobject.scale); + var realpos = this.pos.copy(); + realpos.x = realpos.x * dim.x + (dim.x/2); + realpos.y = realpos.y * dim.y + (dim.y/2); + return cwh2bb(realpos,dim); + }, + + sync() { + if (this.path) + cmd(12,this.id,this.path,this.rect); + }, + + kill() { cmd(9,this.id); }, + }); + sprite = new Proxy(sprite, sync_proxy); + sprite.obscure('boundingbox'); + return sprite; + }, input_kp7_pressed() { this.pos = [-1,0]; }, input_kp6_pressed() { this.pos = [0,-0.5]; }, @@ -40,43 +76,6 @@ var sprite = clone(component, { input_kp3_pressed() { this.pos = [0, -1]; }, input_kp2_pressed() { this.pos = [-0.5,-1]; }, input_kp1_pressed() { this.pos = [-1,-1]; }, - - make(go) { - var old = this; - var sprite = clone(this, { - get enabled() { return cmd(114,this.id); }, - set enabled(x) { cmd(20,this.id,x); }, - set color(x) { cmd(96,this.id,x); }, - get pos() { return cmd(111, this.id); }, - set pos(x) { cmd(37,this.id,x); }, - set layer(x) { cmd(60, this.id, x); }, - get layer() { return this.gameobject.draw_layer; }, - set path(x) { cmd(12,this.id,x,this.rect); }, - - get boundingbox() { - var dim = cmd(64,this.path); - dim = dim.scale(this.gameobject.scale); - var realpos = this.pos.copy(); - realpos.x = realpos.x * dim.x + (dim.x/2); - realpos.y = realpos.y * dim.y + (dim.y/2); - return cwh2bb(realpos,dim); - }, - - kill() { cmd(9,this.id); }, - }); - - sprite.obscure('boundingbox'); - - var id = make_sprite(go,old.path,old.pos); - - Object.defineProperty(sprite, 'id', {value:id}); - - Object.assign(sprite, this); - - return sprite; - }, - - }); /* Container to play sprites and anim2ds */ @@ -100,10 +99,39 @@ var char2d = clone(sprite, { }, make(go) { - var char = clone(this); + var char = clone(this, { + get enabled() { return cmd(114,this.id); }, + set enabled(x) { cmd(20,this.id,x); }, + set color(x) { cmd(96,this.id,x); }, + get pos() { return cmd(111, this.id); }, + set pos(x) { cmd(37,this.id,x); }, + set layer(x) { cmd(60, this.id, x); }, + get layer() { return this.gameobject.draw_layer; }, + + get boundingbox() { + var dim = cmd(64,this.path); + dim = dim.scale(this.gameobject.scale); + dim.x *= 1/6; + var realpos = [0,0]; +// var realpos = this.pos.slice(); + +// realpos.x = realpos.x * dim.x + (dim.x/2); +// realpos.y = realpos.y * dim.y + (dim.y/2); + return cwh2bb(realpos,dim); + }, + + sync() { + if (this.path) + cmd(12,this.id,this.path,this.rect); + }, + + kill() { cmd(9,this.id); }, + }); + char.curplaying = char.anims.array()[0]; char.obscure('curplaying'); - Object.defineProperty(char, 'id', {value:make_sprite(go,char.curplaying.path,this.pos)}); + char.id = make_sprite(go, char.curplaying.path, this.pos); + char.obscure('id'); char.frame = 0; char.timer = timer.make(char.advance.bind(char), 1/char.curplaying.fps); @@ -254,11 +282,12 @@ var polygon2d = clone(collider2d, { complete_assign(poly, this.make_fns); complete_assign(poly, { get boundingbox() { - return points2bb(this.points.map(x => x.scale(this.gameobject.scale))); + return points2bb(this.spoints); }, sync() { cmd_poly2d(0, this.id, this.spoints); } }); + poly.obscure('boundingbox'); poly.defn('points', this.points.copy()); @@ -268,6 +297,8 @@ var polygon2d = clone(collider2d, { Object.defineProperty(poly, 'id', {enumerable:false}); Object.defineProperty(poly, 'shape', {enumerable:false}); + poly.sync(); + return poly; }, @@ -278,7 +309,6 @@ var polygon2d = clone(collider2d, { this.points = sortpointsccw(this.points); }, - input_b_pressed() { if (!Keys.ctrl()) return; diff --git a/source/scripts/debug.js b/source/scripts/debug.js index 9d86fc0..a681767 100644 --- a/source/scripts/debug.js +++ b/source/scripts/debug.js @@ -55,7 +55,8 @@ var Debug = { register_debug(fn,obj); }, - line(points, color, type) { + line(points, color, type, thickness) { + thickness ??= 1; if (!type) type = 0; @@ -64,7 +65,7 @@ var Debug = { switch (type) { case 0: - cmd(83, points, color); + cmd(83, points, color, thickness); } }, }; diff --git a/source/scripts/engine.js b/source/scripts/engine.js index 0661540..77abfb1 100644 --- a/source/scripts/engine.js +++ b/source/scripts/engine.js @@ -1,8 +1,5 @@ var files = {}; function load(file) { - if (typeof Log !== 'undefined') - Log.warn(`doing ${file}`); - var modtime = cmd(0, file); if (modtime === 0) { @@ -12,6 +9,17 @@ function load(file) { files[file] = modtime; } +function run(file) +{ + var modtime = cmd(117,file); + if (modtime === 0) { + Log.stack(); + return false; + } + + files[file] = modtime; +} + load("scripts/base.js"); var Log = { @@ -167,6 +175,16 @@ var Yugine = { }; var timer = { + guardfn(fn) { + if (typeof fn === 'function') + fn(); + else { + Log.warn("TIMER TRYING TO EXECUTE WIHTOUT!!!"); + Log.warn(this); + this.kill(); + } + }, + make(fn, secs,obj,loop) { if (secs === 0) { fn.call(obj); @@ -174,7 +192,7 @@ var timer = { } var t = clone(this); - t.id = make_timer(fn, secs, obj); + t.id = make_timer(this.guardfn.bind(t,fn), secs, obj); return t; }, @@ -484,6 +502,7 @@ var Register = { nk_guis: [], nk_gui() { + this.nk_guis.forEach(x => x[0].call(x[1])); }, @@ -514,12 +533,13 @@ var Register = { }, unregister_obj(obj) { +// Log.warn(`Unregister ${JSON.stringify(obj)}`); this.updates = this.updates.filter(x => x[1] !== obj); this.guis = this.guis.filter(x => x[1] !== obj); this.nk_guis = this.nk_guis.filter(x => x[1] !== obj); this.debugs = this.debugs.filter(x => x[1] !== obj); this.physupdates = this.physupdates.filter(x => x[1] !== obj); - + this.draws = this.draws.filter(x => x[1] !== obj); Player.players.forEach(x => x.uncontrol(obj)); }, @@ -538,7 +558,6 @@ var Register = { }, }; -Register.unregister_obj(null); register(0, Register.update, Register); register(1, Register.physupdate, Register); register(2, Register.gui, Register); @@ -568,7 +587,7 @@ function register_debug(fn, obj) { }; function unregister_gui(fn, obj) { - Register.guis = Register.guis.filter(x => x[0] !== fn && x[1] !== obj); + Register.guis = Register.guis.filter(x => x[0] !== fn || x[1] !== obj); }; function register_nk_gui(fn, obj) { @@ -608,7 +627,7 @@ var Signal = { register_collide(3,fn,obj,go.body); }, - clear_obj(obj) { + clera_obj(obj) { this.signals.filter(function(x) { return x[1] !== obj; }); }, }; @@ -635,6 +654,7 @@ function reloadfiles() { load("scripts/debug.js"); +/* function Color(from) { var color = Object.create(Array); Object.defineProperty(color, 'r', setelem(0)); @@ -647,6 +667,7 @@ function Color(from) { return color; }; +*/ load("scripts/components.js"); @@ -1266,15 +1287,7 @@ function grab_from_points(pos, points, slop) { }; var gameobject = { - get scale() { return this._scale; }, - set scale(x) { - this._scale = Math.max(0,x); - if (this.body > -1) - cmd(36, this.body, this._scale); - - this.sync(); - }, - _scale: 1.0, + scale: 1.0, save: true, @@ -1427,11 +1440,11 @@ var gameobject = { this.instances.forEach(function(x) { x.sync(); }); }, - pulse(vec) { + pulse(vec) { /* apply impulse */ set_body(4, this.body, vec); }, - push(vec) { + push(vec) { /* apply force */ set_body(12,this.body,vec); }, @@ -1477,6 +1490,18 @@ var gameobject = { return bb ? bb : cwh2bb([0,0], [0,0]); }, + set width(x) {}, + get width() { + var bb = this.boundingbox; + Log.warn(bb); + return bb.r - bb.l; + }, + set height(x) {}, + get height() { + var bb = this.boundingbox; + return bb.t-bb.b; + }, + stop() {}, kill() { @@ -1490,7 +1515,7 @@ var gameobject = { this.uncontrol(); this.instances.remove(this); Register.unregister_obj(this); - Signal.clear_obj(this); +// Signal.clear_obj(this); this.body = -1; for (var key in this.components) { @@ -1538,6 +1563,30 @@ var gameobject = { world2this(pos) { return cmd(70, this.body, pos); }, this2world(pos) { return cmd(71, this.body,pos); }, + check_registers(obj) { + Register.unregister_obj(this); + + if (typeof obj.update === 'function') + register_update(obj.update, obj); + + if (typeof obj.physupdate === 'function') + register_physupdate(obj.physupdate, obj); + + if (typeof obj.collide === 'function') + obj.register_hit(obj.collide, obj); + + if (typeof obj.separate === 'function') + obj.register_separate(obj.separate, obj); + + if (typeof obj.draw === 'function') + register_draw(obj.draw,obj); + + obj.components.forEach(function(x) { + if (typeof x.collide === 'function') + register_collide(1, x.collide, x, obj.body, x.shape); + }); + }, + make(props, level) { level ??= World; var obj = Object.create(this); @@ -1600,25 +1649,7 @@ var gameobject = { } }; - if (typeof obj.update === 'function') - register_update(obj.update, obj); - - if (typeof obj.physupdate === 'function') - register_physupdate(obj.physupdate, obj); - - if (typeof obj.collide === 'function') - obj.register_hit(obj.collide, obj); - - if (typeof obj.separate === 'function') - obj.register_separate(obj.separate, obj); - - if (typeof obj.draw === 'function') - register_draw(obj.draw,obj); - - obj.components.forEach(function(x) { - if (typeof x.collide === 'function') - register_collide(1, x.collide, x, obj.body, x.shape); - }); + obj.check_registers(obj); if ('begin' in obj) obj.begin(); @@ -1641,11 +1672,8 @@ var gameobject = { } -var locks = ['visible', 'body', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name', 'scale', 'angle', 'properties', 'moi', 'relpos', 'relangle', 'up', 'down', 'right', 'left', 'bodytype', 'gizmo', 'pos']; -locks.forEach(function(x) { - Object.defineProperty(gameobject, x, {enumerable:false}); -}); - +var locks = ['height', 'width', 'visible', 'body', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name', 'scale', 'angle', 'properties', 'moi', 'relpos', 'relangle', 'up', 'down', 'right', 'left', 'bodytype', 'gizmo', 'pos']; +locks.forEach(x => gameobject.obscure(x)); /* Load configs */ function load_configs(file) { var configs = JSON.parse(IO.slurp(file)); @@ -1790,3 +1818,5 @@ for (var key in prototypes) { } function save_gameobjects_as_prototypes() { slurpwrite(JSON.stringify(gameobjects,null,2), "proto.json"); }; + +let Gamestate = {}; diff --git a/source/scripts/play.js b/source/scripts/play.js index f37e276..c075110 100644 --- a/source/scripts/play.js +++ b/source/scripts/play.js @@ -3,3 +3,5 @@ if (load("game.js") === false) { quit(); } +sim_start(); +