@silkweaver/build 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,15 +44,33 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  };
45
45
  })();
46
46
  Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.EVENT_ORDER = void 0;
47
48
  exports.parse_object = parse_object;
49
+ exports.this_members = this_members;
48
50
  exports.set_static = set_static;
49
51
  exports.remove_static = remove_static;
50
52
  exports.set_field = set_field;
51
53
  exports.remove_field = remove_field;
52
54
  exports.add_method = add_method;
53
55
  exports.remove_method = remove_method;
56
+ exports.get_event_body = get_event_body;
57
+ exports.set_event_body = set_event_body;
58
+ exports.normalize_object = normalize_object;
54
59
  exports.scaffold_object = scaffold_object;
55
60
  const ts = __importStar(require("typescript"));
61
+ /** Canonical GMS-style order of `on_*` event methods (used to keep events ordered in code). */
62
+ exports.EVENT_ORDER = [
63
+ 'on_create', 'on_destroy',
64
+ 'on_step_begin', 'on_step', 'on_step_end',
65
+ 'on_draw_begin', 'on_draw', 'on_draw_end', 'on_draw_gui',
66
+ 'on_alarm',
67
+ 'on_key_press', 'on_key_release', 'on_key_held',
68
+ 'on_mouse_left_press', 'on_mouse_left_release', 'on_mouse_right_press',
69
+ 'on_collision',
70
+ 'on_room_start', 'on_room_end', 'on_game_start', 'on_game_end',
71
+ 'on_animation_end', 'on_path_end', 'on_outside_room', 'on_intersect_boundary',
72
+ 'on_no_more_lives', 'on_no_more_health', 'on_user',
73
+ ];
56
74
  // =========================================================================
57
75
  // Parsing
58
76
  // =========================================================================
@@ -126,6 +144,42 @@ function _string_literal(node) {
126
144
  return node.text;
127
145
  return null;
128
146
  }
147
+ /**
148
+ * Collects every instance member reachable through `this.` in a class: declared instance fields
149
+ * *plus* members created/assigned at runtime inside any event (e.g. `this.velocity = 0` in on_create,
150
+ * read in on_step). This drives `this.` autocomplete, so a variable made in one event surfaces in all
151
+ * the others — matching the usual split of constants on the object, runtime state in Create. Static
152
+ * fields and method/event names are excluded (engine members are merged in separately).
153
+ */
154
+ function this_members(src) {
155
+ const sf = _source(src);
156
+ const cls = _find_class(sf);
157
+ if (!cls)
158
+ return [];
159
+ const names = new Set();
160
+ for (const m of cls.members) {
161
+ if (ts.isPropertyDeclaration(m) && !_is_static(m)) {
162
+ const n = _name_of(m);
163
+ if (n)
164
+ names.add(n);
165
+ }
166
+ }
167
+ // Any `this.<name> = …` (or compound assignment) anywhere in the class body — including nested
168
+ // blocks/closures — counts as an instance member the user can reference elsewhere.
169
+ const visit = (node) => {
170
+ if (ts.isBinaryExpression(node)
171
+ && node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment
172
+ && node.operatorToken.kind <= ts.SyntaxKind.LastAssignment
173
+ && ts.isPropertyAccessExpression(node.left)
174
+ && node.left.expression.kind === ts.SyntaxKind.ThisKeyword
175
+ && ts.isIdentifier(node.left.name)) {
176
+ names.add(node.left.name.text);
177
+ }
178
+ ts.forEachChild(node, visit);
179
+ };
180
+ visit(cls);
181
+ return [...names];
182
+ }
129
183
  function _apply(src, e) {
130
184
  return src.slice(0, e.start) + e.text + src.slice(e.end);
131
185
  }
@@ -139,6 +193,20 @@ function _insert_member(src, text) {
139
193
  const prefix = pos > 0 && src[pos - 1] !== '\n' ? '\n' : ''; // guard against mashing onto the prior line
140
194
  return _apply(src, { start: pos, end: pos, text: prefix + text });
141
195
  }
196
+ /** Inserts `text` (a full member incl. trailing newline) on its own line before the member at `pos`. */
197
+ function _insert_before(src, pos, text) {
198
+ const line_start = src.lastIndexOf('\n', pos - 1) + 1;
199
+ return src.slice(0, line_start) + text + src.slice(line_start);
200
+ }
201
+ /** Inserts `text` (a full member incl. trailing newline) on its own line after the member ending at `end`. */
202
+ function _insert_after(src, end, text) {
203
+ let i = end;
204
+ while (i < src.length && src[i] !== '\n')
205
+ i++; // to the end of the member's own line
206
+ if (i < src.length && src[i] === '\n')
207
+ i++; // past the line terminator
208
+ return src.slice(0, i) + text + src.slice(i);
209
+ }
142
210
  function _find_member(cls, pred) {
143
211
  return cls.members.find(pred);
144
212
  }
@@ -178,13 +246,25 @@ function set_field(src, name, expr) {
178
246
  if (existing) {
179
247
  return _apply(src, { start: existing.getStart(sf), end: existing.getEnd(), text: `${name} = ${expr}` });
180
248
  }
181
- return _insert_member(src, ` ${name} = ${expr}\n`);
249
+ // New variable: keep all instance variables grouped at the TOP of the class, in add-order, so
250
+ // every event below can reference them — and a later variable's initializer can safely read an
251
+ // earlier one. Insert after the last existing variable; if none, before the first member.
252
+ const stub = ` ${name} = ${expr}\n`;
253
+ const vars = cls.members.filter(m => ts.isPropertyDeclaration(m) && !_is_static(m));
254
+ if (vars.length)
255
+ return _insert_after(src, vars[vars.length - 1].getEnd(), stub);
256
+ if (cls.members.length)
257
+ return _insert_before(src, cls.members[0].getStart(sf), stub);
258
+ return _insert_member(src, stub);
182
259
  }
183
260
  /** Removes an instance variable field. */
184
261
  function remove_field(src, name) {
185
262
  return _remove_member(src, m => ts.isPropertyDeclaration(m) && !_is_static(m) && _name_of(m) === name);
186
263
  }
187
- /** Adds an `on_*` event method stub if it is not already present. */
264
+ /**
265
+ * Adds an `on_*` event method stub if it is not already present, inserted in canonical event
266
+ * order (before the first existing event method that comes later in EVENT_ORDER).
267
+ */
188
268
  function add_method(src, method, params = '', body = '') {
189
269
  const sf = _source(src);
190
270
  const cls = _find_class(sf);
@@ -193,6 +273,17 @@ function add_method(src, method, params = '', body = '') {
193
273
  if (_find_member(cls, m => ts.isMethodDeclaration(m) && _name_of(m) === method))
194
274
  return src;
195
275
  const stub = ` ${method}(${params}): void {\n${body ? ' ' + body + '\n' : ''} }\n`;
276
+ const order = exports.EVENT_ORDER.indexOf(method);
277
+ if (order >= 0) {
278
+ const after = cls.members.find(m => {
279
+ if (!ts.isMethodDeclaration(m))
280
+ return false;
281
+ const n = _name_of(m);
282
+ return !!n && exports.EVENT_ORDER.indexOf(n) > order;
283
+ });
284
+ if (after)
285
+ return _insert_before(src, after.getStart(sf), stub);
286
+ }
196
287
  return _insert_member(src, stub);
197
288
  }
198
289
  /** Removes an event method by name. */
@@ -222,6 +313,196 @@ function _remove_member(src, pred) {
222
313
  return _apply(src, { start, end, text: '' });
223
314
  }
224
315
  // =========================================================================
316
+ // Event body extract / pack — per-event "virtual file" editing
317
+ // =========================================================================
318
+ //
319
+ // An event is edited as its OWN buffer: get_event_body returns just the method's body,
320
+ // de-indented to column 0, so the editor is a normal standalone file (Ctrl+A, auto-indent,
321
+ // autocomplete — no hidden areas, no live brace-matching). set_event_body packs that buffer
322
+ // back into the method, re-indented. Both are AST-based, so they're robust to braces/strings.
323
+ /** Strips common leading indentation + surrounding blank lines (method body → editable buffer). */
324
+ function _dedent(text) {
325
+ const lines = text.replace(/\r\n?/g, '\n').split('\n');
326
+ while (lines.length && lines[0].trim() === '')
327
+ lines.shift();
328
+ while (lines.length && lines[lines.length - 1].trim() === '')
329
+ lines.pop();
330
+ if (lines.length === 0)
331
+ return '';
332
+ let min = Infinity;
333
+ for (const l of lines) {
334
+ if (l.trim() === '')
335
+ continue;
336
+ const w = /^[ \t]*/.exec(l)[0].length;
337
+ if (w < min)
338
+ min = w;
339
+ }
340
+ if (!isFinite(min))
341
+ min = 0;
342
+ return lines.map(l => (l.trim() === '' ? '' : l.slice(min))).join('\n');
343
+ }
344
+ /** Re-applies an indent prefix to each non-blank line (editable buffer → method body). */
345
+ function _indent(text, indent) {
346
+ return text.replace(/\r\n?/g, '\n').split('\n').map(l => (l.trim() === '' ? '' : indent + l)).join('\n');
347
+ }
348
+ /** Returns the leading whitespace of the line containing `pos`. */
349
+ function _line_indent(src, pos) {
350
+ const line_start = src.lastIndexOf('\n', pos - 1) + 1;
351
+ return /^[ \t]*/.exec(src.slice(line_start, pos))?.[0] ?? '';
352
+ }
353
+ function _find_event_method(cls, method) {
354
+ return cls.members.find(m => ts.isMethodDeclaration(m) && _name_of(m) === method);
355
+ }
356
+ /**
357
+ * Returns one event method's body as an editable, de-indented buffer, or null if absent/bodyless.
358
+ * @param method - The on_* method to extract (e.g. 'on_step')
359
+ */
360
+ function get_event_body(src, method) {
361
+ const sf = _source(src);
362
+ const cls = _find_class(sf);
363
+ if (!cls)
364
+ return null;
365
+ const m = _find_event_method(cls, method);
366
+ if (!m || !m.body)
367
+ return null;
368
+ const inner = src.slice(m.body.getStart(sf) + 1, m.body.getEnd() - 1); // between the braces
369
+ return _dedent(inner);
370
+ }
371
+ /**
372
+ * Replaces an event method's body with `body` (an edited, de-indented buffer), re-indenting it to
373
+ * the method's level. Returns the full updated source; a no-op if the method is absent.
374
+ * @param method - The on_* method to update
375
+ * @param body - The new body (column-0 / de-indented, as edited)
376
+ */
377
+ function set_event_body(src, method, body) {
378
+ const sf = _source(src);
379
+ const cls = _find_class(sf);
380
+ if (!cls)
381
+ return src;
382
+ const m = _find_event_method(cls, method);
383
+ if (!m || !m.body)
384
+ return src;
385
+ const open = m.body.getStart(sf); // position of '{'
386
+ const close = m.body.getEnd() - 1; // position of '}'
387
+ const method_indent = _line_indent(src, m.getStart(sf));
388
+ const dedented = _dedent(body);
389
+ const inner = dedented === '' ? '' : _indent(dedented, method_indent + ' ');
390
+ const block = '{\n' + (inner ? inner + '\n' : '') + method_indent + '}';
391
+ return src.slice(0, open) + block + src.slice(close + 1);
392
+ }
393
+ // =========================================================================
394
+ // Normalize — variables-first + canonical event order + indentation (full class view)
395
+ // =========================================================================
396
+ /**
397
+ * Re-indents code by bracket/brace/paren depth (leading whitespace only — never touches content or
398
+ * spacing). Strings and comments are skipped. Good for typical game code; deeply nested `({`-style
399
+ * continuations may be off by a level (cosmetic, not corrupting).
400
+ */
401
+ function _reindent(code, unit = ' ') {
402
+ const lines = code.replace(/\r\n?/g, '\n').split('\n');
403
+ const out = [];
404
+ let depth = 0;
405
+ let in_block = false; // inside a /* … */ comment spanning lines
406
+ for (const raw of lines) {
407
+ const line = raw.trim();
408
+ if (line === '') {
409
+ out.push('');
410
+ continue;
411
+ }
412
+ let delta = 0, first_is_closer = false, started = false;
413
+ let i = 0;
414
+ let line_block = in_block;
415
+ while (i < line.length) {
416
+ if (line_block) {
417
+ if (line[i] === '*' && line[i + 1] === '/') {
418
+ line_block = false;
419
+ i += 2;
420
+ continue;
421
+ }
422
+ i++;
423
+ continue;
424
+ }
425
+ const c = line[i], n = line[i + 1];
426
+ if (c === '/' && n === '/')
427
+ break;
428
+ if (c === '/' && n === '*') {
429
+ line_block = true;
430
+ i += 2;
431
+ continue;
432
+ }
433
+ if (c === '"' || c === "'" || c === '`') {
434
+ const q = c;
435
+ i++;
436
+ while (i < line.length && line[i] !== q) {
437
+ if (line[i] === '\\')
438
+ i++;
439
+ i++;
440
+ }
441
+ i++;
442
+ continue;
443
+ }
444
+ const opener = c === '{' || c === '(' || c === '[';
445
+ const closer = c === '}' || c === ')' || c === ']';
446
+ if (opener)
447
+ delta++;
448
+ else if (closer)
449
+ delta--;
450
+ if (!started && (opener || closer || /\S/.test(c))) {
451
+ started = true;
452
+ if (closer)
453
+ first_is_closer = true;
454
+ }
455
+ i++;
456
+ }
457
+ in_block = line_block;
458
+ const this_depth = Math.max(0, depth - (first_is_closer ? 1 : 0));
459
+ out.push(unit.repeat(this_depth) + line);
460
+ depth = Math.max(0, depth + delta);
461
+ }
462
+ return out.join('\n');
463
+ }
464
+ /**
465
+ * Reorders all class members into canonical groups, preserving each member's attached comments:
466
+ * 1. instance variables — in their existing relative order (field-init order can matter; one
467
+ * field's initializer may read another), so they're grouped but never shuffled among themselves;
468
+ * 2. `on_*` event methods — in canonical EVENT_ORDER;
469
+ * 3. everything else (statics, helper methods) — in place.
470
+ * Variables-before-events is the whole point: every event can reference every variable. A no-op if
471
+ * the order is already canonical.
472
+ */
473
+ function _reorder_members(src) {
474
+ const sf = _source(src);
475
+ const cls = _find_class(sf);
476
+ if (!cls || cls.members.length <= 1)
477
+ return src;
478
+ const members = cls.members;
479
+ const event_idx = (m) => {
480
+ const n = _name_of(m);
481
+ return n && ts.isMethodDeclaration(m) ? exports.EVENT_ORDER.indexOf(n) : -1;
482
+ };
483
+ const is_var = (m) => ts.isPropertyDeclaration(m) && !_is_static(m);
484
+ const vars = members.filter(is_var);
485
+ const events = members.filter(m => event_idx(m) >= 0).sort((a, b) => event_idx(a) - event_idx(b));
486
+ const others = members.filter(m => !is_var(m) && event_idx(m) < 0);
487
+ const ordered = [...vars, ...events, ...others];
488
+ if (ordered.every((m, i) => m === members[i]))
489
+ return src; // already canonical
490
+ // Rebuild the class body from each member's full text (getFullStart → getEnd includes the leading
491
+ // trivia — newlines/indent/comments — so comments travel with their member). The class header up
492
+ // to `{` and the closing `}` + trailing trivia are preserved verbatim.
493
+ const chunk = (m) => src.slice(m.getFullStart(), m.getEnd());
494
+ const prefix = src.slice(0, members[0].getFullStart());
495
+ const suffix = src.slice(members[members.length - 1].getEnd());
496
+ return prefix + ordered.map(chunk).join('') + suffix;
497
+ }
498
+ /**
499
+ * Normalizes a class-per-object file for display in the full code view: variables hoisted above all
500
+ * events, canonical event order, then consistent indentation. Unchanged if there's no class.
501
+ */
502
+ function normalize_object(src) {
503
+ return _reindent(_reorder_members(src));
504
+ }
505
+ // =========================================================================
225
506
  // Scaffolding
226
507
  // =========================================================================
227
508
  /** Generates a minimal class-file source for a new object. Imports are auto-managed (none needed). */
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Starter templates for the New Project flow.
3
+ *
4
+ * Each template is a real, ready-to-build project folder bundled under `../templates/<id>/`
5
+ * (sprites and all). Creating a project from a template is just a recursive copy of that folder
6
+ * into the destination — no codegen — so the templates stay editable/diffable in the repo and a
7
+ * dogfooded example game *is* the template. Pure Node (fs/path), reusable from the IDE or CLI.
8
+ */
9
+ export interface template_info {
10
+ id: string;
11
+ label: string;
12
+ description: string;
13
+ display_color: string;
14
+ }
15
+ /** Returns the available starter templates — only those whose folder is actually installed. */
16
+ export declare function list_templates(): template_info[];
17
+ /**
18
+ * Materializes a starter template into `dest_folder` by recursively copying its bundled project
19
+ * folder, then (optionally) setting the display name in project.json.
20
+ * @param template_id - 'empty' | 'platformer' | 'topdown'
21
+ * @param dest_folder - Absolute path of the new project folder (created if missing)
22
+ * @param name - Optional display name to write into project.json
23
+ */
24
+ export declare function create_from_template(template_id: string, dest_folder: string, name?: string): Promise<void>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * Starter templates for the New Project flow.
4
+ *
5
+ * Each template is a real, ready-to-build project folder bundled under `../templates/<id>/`
6
+ * (sprites and all). Creating a project from a template is just a recursive copy of that folder
7
+ * into the destination — no codegen — so the templates stay editable/diffable in the repo and a
8
+ * dogfooded example game *is* the template. Pure Node (fs/path), reusable from the IDE or CLI.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.list_templates = list_templates;
45
+ exports.create_from_template = create_from_template;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const build_js_1 = require("./build.js");
49
+ /** Ordered registry of the bundled starter templates (folders live under ../templates/<id>). */
50
+ const TEMPLATE_REGISTRY = [
51
+ { id: 'empty', label: 'Empty', description: 'A blank project with a single empty room.' },
52
+ { id: 'platformer', label: 'Platformer', description: 'A/D to move, Space to jump — gravity, solid platforms, parent-based collision.' },
53
+ { id: 'topdown', label: 'Top-down', description: 'WASD movement with parent-based wall collision (place_meeting + a _col parent).' },
54
+ ];
55
+ /** Absolute path to the bundled templates directory (sibling of dist/). */
56
+ function templates_dir() {
57
+ return path.join(__dirname, '..', 'templates');
58
+ }
59
+ /** Names never copied into a new project (build artifacts / VCS / deps), as a defensive guard. */
60
+ const COPY_DENYLIST = new Set(['_entry.ts', '_engine_globals.ts', 'node_modules', '.git', 'exports', 'game.js', '.engine']);
61
+ /** Returns the available starter templates — only those whose folder is actually installed. */
62
+ function list_templates() {
63
+ const out = [];
64
+ for (const t of TEMPLATE_REGISTRY) {
65
+ let display_color = '#1a1a2e';
66
+ try {
67
+ const j = JSON.parse(fs.readFileSync(path.join(templates_dir(), t.id, 'project.json'), 'utf8'));
68
+ if (typeof j?.settings?.displayColor === 'string')
69
+ display_color = j.settings.displayColor;
70
+ }
71
+ catch {
72
+ continue;
73
+ } // folder missing/unreadable → omit it
74
+ out.push({ id: t.id, label: t.label, description: t.description, display_color });
75
+ }
76
+ return out;
77
+ }
78
+ /**
79
+ * Materializes a starter template into `dest_folder` by recursively copying its bundled project
80
+ * folder, then (optionally) setting the display name in project.json.
81
+ * @param template_id - 'empty' | 'platformer' | 'topdown'
82
+ * @param dest_folder - Absolute path of the new project folder (created if missing)
83
+ * @param name - Optional display name to write into project.json
84
+ */
85
+ async function create_from_template(template_id, dest_folder, name) {
86
+ if (!TEMPLATE_REGISTRY.some(t => t.id === template_id))
87
+ throw new Error(`Unknown template: ${template_id}`);
88
+ const src = path.join(templates_dir(), template_id);
89
+ if (!fs.existsSync(path.join(src, 'project.json')))
90
+ throw new Error(`Template '${template_id}' is not installed`);
91
+ if (fs.existsSync(path.join(dest_folder, 'project.json')))
92
+ throw new Error('A project already exists in that folder');
93
+ await fs.promises.mkdir(dest_folder, { recursive: true });
94
+ await fs.promises.cp(src, dest_folder, {
95
+ recursive: true,
96
+ filter: (s) => !COPY_DENYLIST.has(path.basename(s)),
97
+ });
98
+ if (name && name.trim()) {
99
+ const proj_path = path.join(dest_folder, 'project.json');
100
+ try {
101
+ const j = JSON.parse(await fs.promises.readFile(proj_path, 'utf8'));
102
+ j.name = name.trim();
103
+ await fs.promises.writeFile(proj_path, JSON.stringify(j, null, 2) + '\n', 'utf8');
104
+ }
105
+ catch { /* keep the template's bundled name */ }
106
+ }
107
+ // Vendor the current engine into the new project so it's pinned to this version (a later IDE
108
+ // update won't change it). Best-effort: if it fails, the project still builds against the
109
+ // toolchain engine via the resolve_engine fallback.
110
+ try {
111
+ await (0, build_js_1.vendor_engine)(dest_folder);
112
+ }
113
+ catch { /* falls back to the toolchain engine */ }
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@silkweaver/build",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Silkweaver toolchain — compiles a project folder into a runnable game (HTML5 / desktop executable). Usable from a CLI or the IDE.",
5
5
  "type": "commonjs",
6
6
  "license": "GPL-3.0",
@@ -23,14 +23,15 @@
23
23
  },
24
24
  "files": [
25
25
  "dist",
26
- "assets"
26
+ "assets",
27
+ "templates"
27
28
  ],
28
29
  "scripts": {
29
30
  "build": "tsc -p ."
30
31
  },
31
32
  "dependencies": {
32
- "@silkweaver/engine": "1.0.0",
33
- "@silkweaver/project": "1.0.0",
33
+ "@silkweaver/engine": "^1.0.0",
34
+ "@silkweaver/project": "^1.0.0",
34
35
  "@electron/packager": "^19.0.5",
35
36
  "esbuild": "^0.27.2",
36
37
  "typescript": "^5.9.3"
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "Empty",
3
+ "version": "1.0.0",
4
+ "engineVersion": "1.0.0",
5
+ "settings": {
6
+ "roomSpeed": 60,
7
+ "windowWidth": 640,
8
+ "windowHeight": 480,
9
+ "startRoom": "room_main",
10
+ "displayColor": "#1a1a2e"
11
+ },
12
+ "resources": {
13
+ "sprites": {},
14
+ "sounds": {},
15
+ "backgrounds": {},
16
+ "paths": {},
17
+ "scripts": {},
18
+ "fonts": {},
19
+ "timelines": {},
20
+ "objects": {},
21
+ "rooms": {
22
+ "room_main": {
23
+ "name": "room_main"
24
+ }
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "width": 640,
3
+ "height": 480,
4
+ "room_speed": 60,
5
+ "persistent": false,
6
+ "creation_code": "",
7
+ "instances": [],
8
+ "backgrounds": [],
9
+ "views": [],
10
+ "tiles": [],
11
+ "bg_color": "#1a1a2e",
12
+ "bg_show_color": true,
13
+ "physics_world": false,
14
+ "physics_gravity_x": 0,
15
+ "physics_gravity_y": 10
16
+ }
@@ -0,0 +1,3 @@
1
+ export class _col extends gm_object {
2
+ static visible = false
3
+ }
@@ -0,0 +1,10 @@
1
+ export class obj_platform extends gm_object {
2
+ on_create(): void {
3
+ }
4
+
5
+ // block width
6
+ // block height
7
+
8
+ static parent = _col
9
+ static sprite = 'spr_platform'
10
+ }
@@ -0,0 +1,73 @@
1
+ export class obj_player extends gm_object {
2
+ spd = 5;
3
+ jump_force = 15;
4
+ weight = 1;
5
+
6
+ on_create(): void {
7
+ this.grounded = false;
8
+ this.vertical_acc = 0.0;
9
+ this.vertical_vel = 0.0;
10
+ this.horizontal_vel = 0.0;
11
+ }
12
+
13
+ on_step(): void {
14
+ // controlls
15
+ let key_left = keyboard_check(ord("A"));
16
+ let key_right = keyboard_check(ord("D"));
17
+ let key_jump = keyboard_check(vk_space);
18
+
19
+ // ------------------------------
20
+ // left to right
21
+ // ------------------------------
22
+
23
+ // motion
24
+ let dir = 0;
25
+ if (key_left) {
26
+ dir = -1;
27
+ }
28
+
29
+ if (key_right) {
30
+ dir = 1;
31
+ }
32
+
33
+ // movenemt
34
+ this.horizontal_vel = dir * this.spd;
35
+ if (!this.place_meeting(this.x + this.horizontal_vel, this.y, _col)) {
36
+ this.x += this.horizontal_vel;
37
+ }
38
+
39
+ // ------------------------------
40
+ // jump and gravity
41
+ // ------------------------------
42
+
43
+ // grounding
44
+ if (this.place_meeting(this.x, this.y + 1, _col)) {
45
+ this.grounded = true;
46
+ this.vertical_vel = 0;
47
+ this.vertical_acc = 0;
48
+ } else {
49
+ this.grounded = false;
50
+ }
51
+
52
+ // vertical acceleration (gravity pulls down while airborne)
53
+ if (!this.grounded) {
54
+ this.vertical_acc = -this.weight;
55
+ }
56
+
57
+ // jump
58
+ if (key_jump && this.grounded) {
59
+ this.vertical_acc = this.jump_force;
60
+ }
61
+
62
+ // apply acceleration, then move — snapping flush against anything in the way
63
+ this.vertical_vel += this.vertical_acc;
64
+ if (this.place_meeting(this.x, this.y - this.vertical_vel, _col)) {
65
+ while (!this.place_meeting(this.x, this.y - sign(this.vertical_vel), _col)) {
66
+ this.y -= sign(this.vertical_vel);
67
+ }
68
+ this.vertical_vel = 0;
69
+ }
70
+ this.y -= this.vertical_vel;
71
+ }
72
+ static sprite = 'spr_player'
73
+ }