Rework Register and Player objects to remove some global functions
This commit is contained in:
parent
aad89926d9
commit
9dc04f6ce7
12
docs/game.md
12
docs/game.md
|
@ -25,9 +25,17 @@ associated script file can access.
|
|||
* F6 game.lvl
|
||||
* F7 Currently edited level
|
||||
|
||||
While playing ...
|
||||
## Playing, editing, debugging
|
||||
|
||||
* F7 Stop
|
||||
Playing is playing your game. Controls are what are specified for your game.
|
||||
|
||||
In debug builds, additional debug controls are available. For example, F12 brings up GUI debug boxes. C-M-f puts you into a flying camera mode, without pausing your game.
|
||||
|
||||
The game can be paused to edit it. Most editor controls are available here, all of them essentially except for loading new levels, clearing the level, etc. An object can be clicked on and edited, objects can be moved, etc.
|
||||
|
||||
A prefab can be opened up to edit on its own, without breaking the currently played level.
|
||||
|
||||
In edit mode, there are no running scripts; only editing them.
|
||||
|
||||
## Level model
|
||||
The game world is made up of objects. Levels are collections of
|
||||
|
|
|
@ -1039,7 +1039,7 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
|
||||
case 118:
|
||||
str = JS_ToCString(js,argv[1]);
|
||||
return bb2js(text_bb(str, js2number(argv[2]), js2number(argv[3]), 1.0));
|
||||
ret = bb2js(text_bb(str, js2number(argv[2]), js2number(argv[3]), 1.0));
|
||||
break;
|
||||
|
||||
case 119:
|
||||
|
@ -1050,6 +1050,9 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
|
|||
case 120:
|
||||
ret = str2js(engine_info());
|
||||
break;
|
||||
case 121:
|
||||
return num2js(get_timescale());
|
||||
break;
|
||||
}
|
||||
|
||||
if (str)
|
||||
|
|
|
@ -309,3 +309,8 @@ void sim_step() {
|
|||
void set_timescale(float val) {
|
||||
timescale = val;
|
||||
}
|
||||
|
||||
double get_timescale()
|
||||
{
|
||||
return timescale;
|
||||
}
|
||||
|
|
|
@ -701,4 +701,3 @@ function sortpointsccw(points)
|
|||
|
||||
return ccw.map(function(x) { return x.add(cm); });
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ var Debug = {
|
|||
},
|
||||
|
||||
register_call(fn, obj) {
|
||||
register_debug(fn,obj);
|
||||
Register.debug.register(fn,obj);
|
||||
},
|
||||
|
||||
line(points, color, type, thickness) {
|
||||
|
@ -103,8 +103,8 @@ var Debug = {
|
|||
: Game.stepping() ?
|
||||
"STEP" :
|
||||
Game.paused() ?
|
||||
"PAUSED" :
|
||||
"STOPPED", [0, 0], 1);
|
||||
"PAUSED; EDITING" :
|
||||
"EDIT", [0, 0], 1);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -223,6 +223,7 @@ var Nuke = {
|
|||
Object.defineProperty(Nuke, "curwin", {enumerable:false});
|
||||
Object.defineProperty(Nuke, "defaultrect", {enumerable:false});
|
||||
|
||||
/* These controls are available during editing, and during play of debug builds */
|
||||
var DebugControls = {};
|
||||
DebugControls.inputs = {};
|
||||
DebugControls.inputs.f1 = function () { Debug.draw_phys(!Debug.phys_drawing); };
|
||||
|
@ -245,12 +246,16 @@ Render.normal.doc = "Render mode for enabling all shaders and lighting effects."
|
|||
DebugControls.inputs['M-2'] = Render.wireframe;
|
||||
Render.wireframe.doc = "Render mode to see wireframes of all models.";
|
||||
|
||||
DebugControls.inputs['C-M-f'] = function() {};
|
||||
DebugControls.inputs['C-M-f'].doc = "Enter camera fly mode.";
|
||||
|
||||
var Time = {
|
||||
set timescale(x) { cmd(3, x); },
|
||||
get timescale() { return cmd(121); },
|
||||
set updateMS(x) { cmd(6, x); },
|
||||
set physMS(x) { cmd(7, x); },
|
||||
set renderMS(x) { cmd(5, x); },
|
||||
};
|
||||
|
||||
set_pawn(DebugControls);
|
||||
register_gui(Debug.draw, Debug);
|
||||
Player.players[0].control(DebugControls);
|
||||
Register.gui.register(Debug.draw, Debug);
|
||||
|
|
|
@ -531,8 +531,8 @@ var editor = {
|
|||
this.edit_level.kill();
|
||||
load_configs("game.config");
|
||||
Game.play();
|
||||
unset_pawn(this);
|
||||
set_pawn(limited_editor);
|
||||
Player.players[0].uncontrol(this);
|
||||
Player.players[0].control(limited_editor);
|
||||
Register.unregister_obj(this);
|
||||
},
|
||||
|
||||
|
@ -785,13 +785,13 @@ var editor = {
|
|||
get sel_comp() { return this._sel_comp; },
|
||||
set sel_comp(x) {
|
||||
if (this._sel_comp)
|
||||
unset_pawn(this._sel_comp);
|
||||
Player.players[0].uncontrol(this._sel_comp);
|
||||
|
||||
this._sel_comp = x;
|
||||
|
||||
if (this._sel_comp) {
|
||||
Log.info("sel comp is now " + this._sel_comp);
|
||||
set_pawn(this._sel_comp);
|
||||
Player.players[0].control(this._sel_comp);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1241,7 +1241,7 @@ editor.inputs['C-space'] = function() {
|
|||
editor.inputs['C-space'].doc = "Search to execute a specific command.";
|
||||
|
||||
editor.inputs['M-m'] = function() {
|
||||
// set_pawn(rebinder);
|
||||
// Player.players[0].control(rebinder);
|
||||
};
|
||||
editor.inputs['M-m'].doc = "Rebind a shortcut. Usage: M-m SHORTCUT TARGET";
|
||||
|
||||
|
@ -1593,8 +1593,8 @@ var inputpanel = {
|
|||
this.value = "";
|
||||
if (steal) {
|
||||
this.stolen = steal;
|
||||
unset_pawn(this.stolen);
|
||||
set_pawn(this);
|
||||
Player.players[0].uncontrol(this.stolen);
|
||||
Player.players[0].control(this);
|
||||
}
|
||||
this.start();
|
||||
this.keycb();
|
||||
|
@ -1604,9 +1604,9 @@ var inputpanel = {
|
|||
|
||||
|
||||
close() {
|
||||
unset_pawn(this);
|
||||
Player.players[0].uncontrol(this);
|
||||
if (this.stolen) {
|
||||
set_pawn(this.stolen);
|
||||
Player.players[0].control(this.stolen);
|
||||
this.stolen = null;
|
||||
}
|
||||
|
||||
|
@ -2457,7 +2457,7 @@ var texgui = clone(inputpanel, {
|
|||
});
|
||||
|
||||
var levellistpanel = copy(inputpanel, {
|
||||
title: "Level list",
|
||||
title: "Level object list",
|
||||
level: {},
|
||||
start() {
|
||||
this.level = editor.edit_level;
|
||||
|
@ -2521,8 +2521,8 @@ limited_editor.inputs['C-q'] = function()
|
|||
Game.stop();
|
||||
game.stop();
|
||||
Sound.killall();
|
||||
unset_pawn(limited_editor);
|
||||
set_pawn(editor);
|
||||
Player.players[0].uncontrol(limited_editor);
|
||||
Player.players[0].control(editor);
|
||||
register_gui(editor.ed_gui, editor);
|
||||
Debug.register_call(editor.ed_debug, editor);
|
||||
World.kill();
|
||||
|
@ -2531,8 +2531,12 @@ limited_editor.inputs['C-q'] = function()
|
|||
Yugine.view_camera(editor_camera);
|
||||
}
|
||||
|
||||
set_pawn(editor);
|
||||
register_gui(editor.ed_gui, editor);
|
||||
/* This is used for editing during a paused game */
|
||||
var limited_editing = {};
|
||||
limited_editing.inputs = {};
|
||||
|
||||
Player.players[0].control(editor);
|
||||
Register.gui.register(editor.ed_gui, editor);
|
||||
Debug.register_call(editor.ed_debug, editor);
|
||||
|
||||
if (IO.exists("editor.config"))
|
||||
|
|
|
@ -146,40 +146,6 @@ var Physics = {
|
|||
static: 2,
|
||||
};
|
||||
|
||||
Physics.stop = function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
function win_icon(str) {
|
||||
cmd(90, str);
|
||||
};
|
||||
|
||||
function sim_start() {
|
||||
Log.warn("Call Game.play() now.");
|
||||
Game.play();
|
||||
/*
|
||||
Game.objects.forEach(function(x) {
|
||||
if (x.start) x.start(); });
|
||||
|
||||
Level.levels.forEach(function(lvl) {
|
||||
lvl.run();
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function sim_stop() { Log.warn("Call through Game.stop()"); Game.stop();}
|
||||
function sim_pause() { Log.warn("Call Game.pause()"); Game.pause(); }
|
||||
function sim_step() { Log.warn("Call Game.step()"); Game.step(); }
|
||||
function sim_playing() { Log.warn("Call Game.playing"); return Game.playing(); }
|
||||
function sim_paused() { Log.warn("Call Game.paused"); return Game.paused(); }
|
||||
function phys_stepping() { Log.warn("Call Game.stepping"); return Game.stepping(); }
|
||||
|
||||
function quit() {
|
||||
Log.warn("Call through Game.quit() now.");
|
||||
Game.quit();
|
||||
};
|
||||
|
||||
var Color = {
|
||||
white: [255,255,255,255],
|
||||
blue: [84,110,255,255],
|
||||
|
@ -612,9 +578,9 @@ var Tween = {
|
|||
|
||||
defn.restart = function() { defn.accum = 0; };
|
||||
defn.stop = function() { defn.pause(); defn.restart(); };
|
||||
defn.pause = function() { unregister_update(defn.fn); };
|
||||
defn.pause = function() { Register.update.unregister(defn.fn); };
|
||||
|
||||
register_update(defn.fn, defn);
|
||||
Register.update.register(defn.fn, defn);
|
||||
|
||||
return defn;
|
||||
},
|
||||
|
@ -662,7 +628,6 @@ var Tween = {
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
var animation = {
|
||||
time: 0,
|
||||
loop: false,
|
||||
|
@ -672,7 +637,7 @@ var animation = {
|
|||
|
||||
create() {
|
||||
var anim = Object.create(animation);
|
||||
register_update(anim.update, anim);
|
||||
Register.update.register(anim.update, anim);
|
||||
return anim;
|
||||
},
|
||||
|
||||
|
@ -736,16 +701,6 @@ var animation = {
|
|||
},
|
||||
};
|
||||
|
||||
var sound = {
|
||||
play() {
|
||||
this.id = cmd(14,this.path);
|
||||
},
|
||||
|
||||
stop() {
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
var Music = {
|
||||
play(path) {
|
||||
Log.info("Playing " + path);
|
||||
|
@ -769,6 +724,7 @@ var Sound = {
|
|||
var s = Object.create(sound);
|
||||
s.path = file;
|
||||
s.play();
|
||||
// this.id = cmd(14,file);
|
||||
return s;
|
||||
},
|
||||
|
||||
|
@ -851,6 +807,18 @@ var Input = {
|
|||
setnuke() { cmd(78); },
|
||||
};
|
||||
|
||||
Input.state2str = function(state) {
|
||||
if (typeof state === 'string') return state;
|
||||
switch (state) {
|
||||
case 0:
|
||||
return "down";
|
||||
case 1:
|
||||
return "pressed";
|
||||
case 2:
|
||||
return "released";
|
||||
}
|
||||
}
|
||||
|
||||
Input.print_pawn_kbm = function(pawn) {
|
||||
if (!('inputs' in pawn)) return;
|
||||
var str = "";
|
||||
|
@ -957,6 +925,15 @@ var Player = {
|
|||
uncontrol(pawn) {
|
||||
this.pawns = this.pawns.filter(x => x !== pawn);
|
||||
},
|
||||
|
||||
obj_controlled(obj) {
|
||||
for (var p in Player.players) {
|
||||
if (p.pawns.contains(obj))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
for (var i = 0; i < 4; i++) {
|
||||
|
@ -966,18 +943,6 @@ for (var i = 0; i < 4; i++) {
|
|||
Player.players.push(player1);
|
||||
}
|
||||
|
||||
function state2str(state) {
|
||||
if (typeof state === 'string') return state;
|
||||
switch (state) {
|
||||
case 0:
|
||||
return "down";
|
||||
case 1:
|
||||
return "pressed";
|
||||
case 2:
|
||||
return "released";
|
||||
}
|
||||
}
|
||||
|
||||
var Register = {
|
||||
inloop: false,
|
||||
loopcbs: [],
|
||||
|
@ -992,27 +957,6 @@ var Register = {
|
|||
this.inloop = false;
|
||||
this.finloop();
|
||||
},
|
||||
|
||||
updates: [],
|
||||
update(dt) {
|
||||
this.wraploop(() => this.updates.forEach(x => x[0].call(x[1], dt)));
|
||||
},
|
||||
|
||||
physupdates: [],
|
||||
physupdate(dt) {
|
||||
this.wraploop(() => this.physupdates.forEach(x => x[0].call(x[1], dt)));
|
||||
},
|
||||
|
||||
guis: [],
|
||||
gui() {
|
||||
this.guis.forEach(x => x[0].call(x[1]));
|
||||
},
|
||||
|
||||
nk_guis: [],
|
||||
nk_gui() {
|
||||
|
||||
this.nk_guis.forEach(x => x[0].call(x[1]));
|
||||
},
|
||||
|
||||
kbm_input(src, btn, state, ...args) {
|
||||
var input = `${src}_${btn}_${state}`;
|
||||
|
@ -1038,7 +982,7 @@ var Register = {
|
|||
var player = this.gamepad_playermap[pad];
|
||||
if (!player) return;
|
||||
|
||||
var statestr = state2str(state);
|
||||
var statestr = Input.state2str(state);
|
||||
|
||||
var rawfn = `gamepad_${btn}_${statestr}`;
|
||||
player.input(rawfn, ...args);
|
||||
|
@ -1048,27 +992,14 @@ var Register = {
|
|||
player.input(`action_${x.name}_${statestr}`, ...args);
|
||||
});
|
||||
},
|
||||
|
||||
debugs: [],
|
||||
debug() {
|
||||
this.debugs.forEach(x => x[0].call(x[1]));
|
||||
},
|
||||
|
||||
|
||||
unregister_obj(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);
|
||||
Register.registries.forEach(function(x) {
|
||||
x.clear();
|
||||
});
|
||||
Player.players.forEach(x => x.uncontrol(obj));
|
||||
},
|
||||
|
||||
draws: [],
|
||||
draw() {
|
||||
this.draws.forEach(x => x[0].call(x[1]));
|
||||
},
|
||||
|
||||
endofloop(fn) {
|
||||
if (!this.inloop)
|
||||
fn();
|
||||
|
@ -1076,68 +1007,57 @@ var Register = {
|
|||
this.loopcbs.push(fn);
|
||||
}
|
||||
},
|
||||
|
||||
clear() {
|
||||
Register.registries.forEach(function(n) {
|
||||
n.entries = [];
|
||||
});
|
||||
},
|
||||
|
||||
registries: [],
|
||||
|
||||
add_cb(idx, name) {
|
||||
var entries = [];
|
||||
var n = {};
|
||||
n.register = function(fn, obj) {
|
||||
entries.push([fn, obj ? obj : null]);
|
||||
}
|
||||
|
||||
n.unregister = function(fn) {
|
||||
entries = entries.filter(function(f) { return fn === f; });
|
||||
}
|
||||
|
||||
n.broadcast = function() {
|
||||
entries.forEach(x => x[0].call(x[1]));
|
||||
}
|
||||
|
||||
n.clear = function() {
|
||||
entries = [];
|
||||
}
|
||||
|
||||
register(idx, n.broadcast, n);
|
||||
|
||||
Register[name] = n;
|
||||
Register.registries.push(n);
|
||||
|
||||
return n;
|
||||
},
|
||||
};
|
||||
|
||||
register(0, Register.update, Register);
|
||||
register(1, Register.physupdate, Register);
|
||||
register(2, Register.gui, Register);
|
||||
register(3, Register.nk_gui, Register);
|
||||
register(6, Register.debug, Register);
|
||||
Register.add_cb(0, "update").doc = "Called once per frame.";
|
||||
Register.add_cb(1, "physupdate");
|
||||
Register.add_cb(2, "gui");
|
||||
Register.add_cb(3, "nk_gui");
|
||||
Register.add_cb(6, "debug");
|
||||
register(7, Register.kbm_input, Register);
|
||||
register(8, Register.gamepad_input, Register);
|
||||
Register.add_cb(8, "gamepad_input");
|
||||
Register.add_cb(10, "draw");
|
||||
|
||||
register(9, Log.stack, this);
|
||||
register(10, Register.draw, Register);
|
||||
|
||||
Register.gamepad_playermap[0] = Player.players[0];
|
||||
|
||||
function register_update(fn, obj) {
|
||||
Register.updates.push([fn, obj ? obj : null]);
|
||||
};
|
||||
|
||||
function unregister_update(fn) {
|
||||
Register.updates = Register.updates.filter(function(updatefn) {
|
||||
return fn === updatefn;
|
||||
});
|
||||
};
|
||||
|
||||
function register_physupdate(fn, obj) {
|
||||
Register.physupdates.push([fn, obj ? obj : null]);
|
||||
};
|
||||
|
||||
function register_gui(fn, obj) {
|
||||
Register.guis.push([fn, obj ? obj : this]);
|
||||
};
|
||||
|
||||
function register_debug(fn, obj) {
|
||||
Register.debugs.push([fn, obj ? obj : this]);
|
||||
};
|
||||
|
||||
function unregister_gui(fn, obj) {
|
||||
Register.guis = Register.guis.filter(x => x[0] !== fn || x[1] !== obj);
|
||||
};
|
||||
|
||||
function register_nk_gui(fn, obj) {
|
||||
Register.nk_guis.push([fn, obj ? obj : this]);
|
||||
};
|
||||
|
||||
function unregister_nk_gui(fn, obj) {
|
||||
Register.nk_guis = Register.nk_guis.filter(x => x[0] !== fn && x[1] !== obj);
|
||||
};
|
||||
|
||||
function register_draw(fn,obj) {
|
||||
Register.draws.push([fn, obj ? obj : this]);
|
||||
}
|
||||
|
||||
register_update(Yugine.exec, Yugine);
|
||||
|
||||
/* These functions are the "defaults", and give control to player0 */
|
||||
function set_pawn(obj, player = Player.players[0]) {
|
||||
player.control(obj);
|
||||
}
|
||||
|
||||
function unset_pawn(obj, player = Player.players[0]) {
|
||||
player.uncontrol(obj);
|
||||
}
|
||||
Register.update.register(Yugine.exec, Yugine);
|
||||
|
||||
Player.players[0].control(GUI);
|
||||
|
||||
|
@ -1177,6 +1097,9 @@ var Window = {
|
|||
dimensions:[0,0],
|
||||
};
|
||||
|
||||
Window.icon = function(path) { cmd(90, path); };
|
||||
Window.icon.doc = "Set the icon of the window using the PNG image at path.";
|
||||
|
||||
Signal.register("window_resize", function(w, h) {
|
||||
Window.width = w;
|
||||
Window.height = h;
|
||||
|
@ -1196,9 +1119,6 @@ var IO = {
|
|||
extensions(ext) { return cmd(66, "." + ext); },
|
||||
};
|
||||
|
||||
function slurp(file) { return IO.slurp(file);}
|
||||
function slurpwrite(str, file) { return IO.slurpwrite(str, file); }
|
||||
|
||||
function reloadfiles() {
|
||||
Object.keys(files).forEach(function (x) { load(x); });
|
||||
}
|
||||
|
@ -1413,7 +1333,7 @@ var Level = {
|
|||
Object.assign(this, extern);
|
||||
|
||||
if (typeof update === 'function')
|
||||
register_update(update, this);
|
||||
Register.update.register(update, this);
|
||||
|
||||
if (typeof gui === 'function')
|
||||
register_gui(gui, this);
|
||||
|
@ -1620,7 +1540,7 @@ var Level = {
|
|||
|
||||
loadlevel(file) {
|
||||
var lvl = Level.loadfile(file);
|
||||
if (lvl && sim_playing())
|
||||
if (lvl && Game.playing())
|
||||
lvl.start();
|
||||
|
||||
return lvl;
|
||||
|
@ -1983,7 +1903,9 @@ var gameobject = {
|
|||
flipy: false,
|
||||
|
||||
body: -1,
|
||||
controlled: false,
|
||||
get controlled() {
|
||||
return Player.obj_controlled(this);
|
||||
},
|
||||
|
||||
set_center(pos) {
|
||||
var change = pos.sub(this.pos);
|
||||
|
@ -2066,16 +1988,6 @@ var gameobject = {
|
|||
|
||||
gizmo: "", /* Path to an image to draw for this gameobject */
|
||||
|
||||
set_pawn() {
|
||||
this.controlled = true;
|
||||
set_pawn(this);
|
||||
},
|
||||
|
||||
uncontrol() {
|
||||
if (!this.controlled) return;
|
||||
unset_pawn(this);
|
||||
},
|
||||
|
||||
/* Bounding box of the object in world dimensions */
|
||||
get boundingbox() {
|
||||
var boxes = [];
|
||||
|
@ -2187,10 +2099,10 @@ var gameobject = {
|
|||
Register.unregister_obj(this);
|
||||
|
||||
if (typeof obj.update === 'function')
|
||||
register_update(obj.update, obj);
|
||||
Register.update.register(obj.update, obj);
|
||||
|
||||
if (typeof obj.physupdate === 'function')
|
||||
register_physupdate(obj.physupdate, obj);
|
||||
Register.physupdate.register(obj.physupdate, obj);
|
||||
|
||||
if (typeof obj.collide === 'function')
|
||||
obj.register_hit(obj.collide, obj);
|
||||
|
@ -2199,10 +2111,10 @@ var gameobject = {
|
|||
obj.register_separate(obj.separate, obj);
|
||||
|
||||
if (typeof obj.draw === 'function')
|
||||
register_draw(obj.draw,obj);
|
||||
Register.draw.register(obj.draw,obj);
|
||||
|
||||
if (typeof obj.debug === 'function')
|
||||
register_debug(obj.debug, obj);
|
||||
Register.debug.register(obj.debug, obj);
|
||||
|
||||
obj.components.forEach(function(x) {
|
||||
if (typeof x.collide === 'function')
|
||||
|
@ -2418,7 +2330,6 @@ Yugine.view_camera = function(cam)
|
|||
Yugine.view_camera(World.spawn(camera2d));
|
||||
|
||||
win_make(Game.title, Game.resolution[0], Game.resolution[1]);
|
||||
//win_icon("icon.png");
|
||||
|
||||
/* Default objects */
|
||||
gameobject.clone("polygon2d", {
|
||||
|
@ -2439,7 +2350,7 @@ if (IO.exists("config.js"))
|
|||
|
||||
var prototypes = {};
|
||||
if (IO.exists("proto.json"))
|
||||
prototypes = JSON.parse(slurp("proto.json"));
|
||||
prototypes = JSON.parse(IO.slurp("proto.json"));
|
||||
|
||||
for (var key in prototypes) {
|
||||
if (key in gameobjects)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
sim_start();
|
||||
Game.play();
|
||||
|
||||
|
||||
if (!IO.exists("game.js"))
|
||||
|
|
Loading…
Reference in a new issue