├── LICENSE
├── README.md
├── habitat-embed.js
├── images
├── Black
│ ├── Other.png
│ ├── Other.psd
│ ├── Other@0.25x.png
│ ├── Other@0.5x.png
│ └── Other@0.75x.png
├── Blank.png
├── Blank.psd
├── Blank@0.25x.png
├── Blank@0.5x.png
├── Blank@0.75x.png
├── Cyan
│ ├── Mad.png
│ ├── Mad.psd
│ ├── Mad@0.25x.png
│ ├── Mad@0.5x.png
│ ├── Mad@0.75x.png
│ ├── Other.png
│ ├── Other.psd
│ ├── Other@0.25x.png
│ ├── Other@0.5x.png
│ └── Other@0.75x.png
├── Green
│ ├── Other.png
│ ├── Other.psd
│ ├── Other@0.25x.png
│ ├── Other@0.5x.png
│ └── Other@0.75x.png
├── Purple
│ ├── Other.png
│ ├── Other.psd
│ ├── Other@0.25x.png
│ ├── Other@0.5x.png
│ └── Other@0.75x.png
└── Yellow
│ ├── Other.png
│ ├── Other.psd
│ ├── Other@0.25x.png
│ ├── Other@0.5x.png
│ └── Other@0.75x.png
├── index.html
└── source
├── atom.js
├── config.js
├── element.js
├── main.js
├── mover.js
├── multiverse.js
└── world.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Luke Wilson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # TimePond
4 | TimePond is a time-travel physics engine.
5 | It lets you play with time-travelling frogs!
6 |
7 | **I made TimePond for this video about time travel: 🌀 [Top 9 ways to make Time Travel](https://youtu.be/Z24NKn6rQRY)**
8 |
9 | My patrons voted for it at [patreon.com/todepond](https://patreon.com/todepond)
10 |
11 | Most of the time-travel experiments are hidden behind URL parameters. Sorry about that!
12 | Here are some things you can try out though:
13 |
14 | | | Simple | Right | Fling | Fall | Bump |
15 | | --- | --- | --- | --- | --- | --- |
16 | | Teleport | [Teleport + Simple](https://timepond.cool?experiment=gsimple&menu=cyan&portal=move) | [Teleport + Right](https://timepond.cool?experiment=gright&menu=cyan&portal=move) | [Teleport + Fling](https://timepond.cool?experiment=gfling&menu=cyan&portal=move) | [Teleport + Fall](https://timepond.cool?experiment=gfall&menu=cyan&portal=move) | [Teleport + Bump](https://timepond.cool?experiment=ggen&menu=cyan&portal=move) |
17 | | Future | [Future + Simple](https://timepond.cool?experiment=gsimple&menu=cyan&portal=futurenow) | [Future + Right](https://timepond.cool?experiment=gright&menu=cyan&portal=futurenow) | [Future + Fling](https://timepond.cool?experiment=gfling&menu=cyan&portal=futurenow) | [Future + Fall](https://timepond.cool?experiment=gfall&menu=cyan&portal=futurenow) | [Future + Bump](https://timepond.cool?experiment=ggen&menu=cyan&portal=futurenow) |
18 | | Past | [Past + Simple](https://timepond.cool?experiment=gsimple&menu=cyan&portal=pastnow) | [Past + Right](https://timepond.cool?experiment=gright&menu=cyan&portal=pastnow) | [Past + Fling](https://timepond.cool?experiment=gfling&menu=cyan&portal=pastnow) | [Past + Fall](https://timepond.cool?experiment=gfall&menu=cyan&portal=pastnow) | [Past + Bump](https://timepond.cool?experiment=ggen&menu=cyan&portal=pastnow) |
19 | | Rewrite | [Rewrite + Simple](https://timepond.cool?experiment=gsimple&menu=purple&portal=rewrite) | [Rewrite + Right](https://timepond.cool?experiment=gright&menu=purple&portal=rewrite) | [Rewrite + Fling](https://timepond.cool?experiment=gfling&menu=purple&portal=rewrite) | [Rewrite + Fall](https://timepond.cool?experiment=gfall&menu=purple&portal=rewrite) | [Rewrite + Bump](https://timepond.cool?experiment=ggen&menu=purple&portal=rewrite) |
20 | | Branch | [Branch + Simple](https://timepond.cool?experiment=gsimple&menu=purple&portal=pastline) | [Branch + Right](https://timepond.cool?experiment=gright&menu=purple&portal=pastline) | [Branch + Fling](https://timepond.cool?experiment=gfling&menu=purple&portal=pastline) | [Branch + Fall](https://timepond.cool?experiment=gfall&menu=purple&portal=pastline) | [Branch + Bump](https://timepond.cool?experiment=ggen&menu=purple&portal=pastline) |
21 |
--------------------------------------------------------------------------------
/habitat-embed.js:
--------------------------------------------------------------------------------
1 | const Habitat = {}
2 |
3 | //=======//
4 | // Array //
5 | //=======//
6 | {
7 |
8 | const install = (global) => {
9 |
10 | Reflect.defineProperty(global.Array.prototype, "last", {
11 | get() {
12 | return this[this.length-1]
13 | },
14 | set(value) {
15 | Reflect.defineProperty(this, "last", {value, configurable: true, writable: true, enumerable: true})
16 | },
17 | configurable: true,
18 | enumerable: false,
19 | })
20 |
21 | Reflect.defineProperty(global.Array.prototype, "clone", {
22 | get() {
23 | return [...this]
24 | },
25 | set(value) {
26 | Reflect.defineProperty(this, "clone", {value, configurable: true, writable: true, enumerable: true})
27 | },
28 | configurable: true,
29 | enumerable: false,
30 | })
31 |
32 | Reflect.defineProperty(global.Array.prototype, "at", {
33 | value(position) {
34 | if (position >= 0) return this[position]
35 | return this[this.length + position]
36 | },
37 | configurable: true,
38 | enumerable: false,
39 | writable: true,
40 | })
41 |
42 | Reflect.defineProperty(global.Array.prototype, "shuffle", {
43 | value() {
44 | for (let i = this.length - 1; i > 0; i--) {
45 | const r = Math.floor(Math.random() * (i+1))
46 | ;[this[i], this[r]] = [this[r], this[i]]
47 | }
48 | return this
49 | },
50 | configurable: true,
51 | enumerable: false,
52 | writable: true,
53 | })
54 |
55 | Reflect.defineProperty(global.Array.prototype, "trim", {
56 | value() {
57 | if (this.length == 0) return this
58 | let start = this.length - 1
59 | let end = 0
60 | for (let i = 0; i < this.length; i++) {
61 | const value = this[i]
62 | if (value !== undefined) {
63 | start = i
64 | break
65 | }
66 | }
67 | for (let i = this.length - 1; i >= 0; i--) {
68 | const value = this[i]
69 | if (value !== undefined) {
70 | end = i + 1
71 | break
72 | }
73 | }
74 | this.splice(end)
75 | this.splice(0, start)
76 | return this
77 | },
78 | configurable: true,
79 | enumerable: false,
80 | writable: true,
81 | })
82 |
83 | Reflect.defineProperty(global.Array.prototype, "repeat", {
84 | value(n) {
85 | if (n === 0) {
86 | this.splice(0)
87 | return this
88 | }
89 | if (n < 0) {
90 | this.reverse()
91 | n = n - n - n
92 | }
93 | const clone = [...this]
94 | for (let i = 1; i < n; i++) {
95 | this.push(...clone)
96 | }
97 | return this
98 | },
99 | configurable: true,
100 | enumerable: false,
101 | writable: true,
102 | })
103 |
104 | Habitat.Array.installed = true
105 |
106 | }
107 |
108 | Habitat.Array = {install}
109 |
110 | }
111 |
112 | //=======//
113 | // Async //
114 | //=======//
115 | {
116 | const sleep = (duration) => new Promise(resolve => setTimeout(resolve, duration))
117 | const install = (global) => {
118 | global.sleep = sleep
119 | Habitat.Async.installed = true
120 | }
121 |
122 | Habitat.Async = {install, sleep}
123 | }
124 |
125 | //========//
126 | // Colour //
127 | //========//
128 | {
129 |
130 | Habitat.Colour = {}
131 |
132 | Habitat.Colour.make = (r, g, b) => {
133 | const colour = new Uint8Array([r, g, b])
134 | const string = `rgb(${r}, ${g}, ${b})`
135 | const hex = `#${r.toString(16)}${g.toString(16)}${b.toString(16)}`
136 | colour.toString = () => string
137 | colour.toHex = () => hex
138 | colour.brightness = (r*299 + g*587 + b*114) / 1000 / 255
139 | colour.isLight = colour.brightness > 0.7
140 | colour.isDark = colour.brightness < 0.3
141 | return colour
142 | }
143 |
144 | Habitat.Colour.Black = Habitat.Colour.make(23, 29, 40)
145 | Habitat.Colour.Grey = Habitat.Colour.make(55, 67, 98)
146 | Habitat.Colour.Silver = Habitat.Colour.make(159, 174, 201)
147 | Habitat.Colour.White = Habitat.Colour.make(242, 245, 247)
148 |
149 | Habitat.Colour.Green = Habitat.Colour.make(70, 255, 128)
150 | Habitat.Colour.Red = Habitat.Colour.make(255, 70, 70)
151 | Habitat.Colour.Blue = Habitat.Colour.make(70, 128, 255)
152 | Habitat.Colour.Yellow = Habitat.Colour.make(255, 204, 70)
153 | Habitat.Colour.Orange = Habitat.Colour.make(255, 128, 70)
154 | Habitat.Colour.Pink = Habitat.Colour.make(255, 128, 128)
155 | Habitat.Colour.Cyan = Habitat.Colour.make(70, 204, 255)
156 | Habitat.Colour.Purple = Habitat.Colour.make(128, 70, 255)
157 |
158 | Habitat.Colour.install = (global) => {
159 | global.Colour = Habitat.Colour
160 | Habitat.Colour.installed = true
161 | }
162 |
163 | }
164 |
165 | //=========//
166 | // Console //
167 | //=========//
168 | {
169 | const print = console.log.bind(console)
170 | const dir = (value) => console.dir(Object(value))
171 |
172 | let print9Counter = 0
173 | const print9 = (message) => {
174 | if (print9Counter >= 9) return
175 | print9Counter++
176 | console.log(message)
177 | }
178 |
179 | const install = (global) => {
180 | global.print = print
181 | global.dir = dir
182 | global.print9 = print9
183 |
184 | Reflect.defineProperty(global.Object.prototype, "d", {
185 | get() {
186 | const value = this.valueOf()
187 | console.log(value)
188 | return value
189 | },
190 | set(value) {
191 | Reflect.defineProperty(this, "d", {value, configurable: true, writable: true, enumerable: true})
192 | },
193 | configurable: true,
194 | enumerable: false,
195 | })
196 |
197 | Reflect.defineProperty(global.Object.prototype, "dir", {
198 | get() {
199 | console.dir(this)
200 | return this.valueOf()
201 | },
202 | set(value) {
203 | Reflect.defineProperty(this, "dir", {value, configurable: true, writable: true, enumerable: true})
204 | },
205 | configurable: true,
206 | enumerable: false,
207 | })
208 |
209 | let d9Counter = 0
210 | Reflect.defineProperty(global.Object.prototype, "d9", {
211 | get() {
212 | const value = this.valueOf()
213 | if (d9Counter < 9) {
214 | console.log(value)
215 | d9Counter++
216 | }
217 | return value
218 | },
219 | set(value) {
220 | Reflect.defineProperty(this, "d9", {value, configurable: true, writable: true, enumerable: true})
221 | },
222 | configurable: true,
223 | enumerable: false,
224 | })
225 |
226 | Habitat.Console.installed = true
227 |
228 | }
229 |
230 | Habitat.Console = {install, print, dir, print9}
231 | }
232 |
233 | //==========//
234 | // Document //
235 | //==========//
236 | {
237 |
238 | const $ = (...args) => document.querySelector(...args)
239 | const $$ = (...args) => document.querySelectorAll(...args)
240 |
241 | const install = (global) => {
242 |
243 |
244 | global.$ = $
245 | global.$$ = $$
246 |
247 | if (global.Node === undefined) return
248 |
249 | Reflect.defineProperty(global.Node.prototype, "$", {
250 | value(...args) {
251 | return this.querySelector(...args)
252 | },
253 | configurable: true,
254 | enumerable: false,
255 | writable: true,
256 | })
257 |
258 | Reflect.defineProperty(global.Node.prototype, "$$", {
259 | value(...args) {
260 | return this.querySelectorAll(...args)
261 | },
262 | configurable: true,
263 | enumerable: false,
264 | writable: true,
265 | })
266 |
267 | Habitat.Document.installed = true
268 |
269 | }
270 |
271 | Habitat.Document = {install, $, $$}
272 |
273 | }
274 |
275 |
276 | //=======//
277 | // Event //
278 | //=======//
279 | {
280 |
281 | const install = (global) => {
282 |
283 | Reflect.defineProperty(global.EventTarget.prototype, "on", {
284 | get() {
285 | return new Proxy(this, {
286 | get: (element, eventName) => (...args) => element.addEventListener(eventName, ...args),
287 | })
288 | },
289 | set(value) {
290 | Reflect.defineProperty(this, "on", {value, configurable: true, writable: true, enumerable: true})
291 | },
292 | configurable: true,
293 | enumerable: false,
294 | })
295 |
296 | Reflect.defineProperty(global.EventTarget.prototype, "trigger", {
297 | value(name, options = {}) {
298 | const {bubbles = true, cancelable = true, ...data} = options
299 | const event = new Event(name, {bubbles, cancelable})
300 | for (const key in data) event[key] = data[key]
301 | this.dispatchEvent(event)
302 | },
303 | configurable: true,
304 | enumerable: false,
305 | writable: true,
306 | })
307 |
308 | Habitat.Event.installed = true
309 |
310 | }
311 |
312 | Habitat.Event = {install}
313 |
314 | }
315 |
316 |
317 | //======//
318 | // HTML //
319 | //======//
320 | {
321 |
322 | Habitat.HTML = (...args) => {
323 | const source = String.raw(...args)
324 | const template = document.createElement("template")
325 | template.innerHTML = source
326 | return template.content
327 | }
328 |
329 | Habitat.HTML.install = (global) => {
330 | global.HTML = Habitat.HTML
331 | Habitat.HTML.installed = true
332 | }
333 |
334 | }
335 |
336 |
337 | //============//
338 | // JavaScript //
339 | //============//
340 | {
341 |
342 | Habitat.JavaScript = (...args) => {
343 | const source = String.raw(...args)
344 | const code = `return ${source}`
345 | const func = new Function(code)()
346 | return func
347 | }
348 |
349 | Habitat.JavaScript.install = (global) => {
350 | global.JavaScript = Habitat.JavaScript
351 | Habitat.JavaScript.installed = true
352 | }
353 |
354 | }
355 |
356 |
357 | //==========//
358 | // Keyboard //
359 | //==========//
360 | {
361 |
362 | const Keyboard = Habitat.Keyboard = {}
363 | Reflect.defineProperty(Keyboard, "install", {
364 | value(global) {
365 | global.Keyboard = Keyboard
366 | global.addEventListener("keydown", e => {
367 | Keyboard[e.key] = true
368 | })
369 |
370 | global.addEventListener("keyup", e => {
371 | Keyboard[e.key] = false
372 | })
373 |
374 | Reflect.defineProperty(Keyboard, "installed", {
375 | value: true,
376 | configurable: true,
377 | enumerable: false,
378 | writable: true,
379 | })
380 | },
381 | configurable: true,
382 | enumerable: false,
383 | writable: true,
384 | })
385 |
386 | }
387 |
388 |
389 | //======//
390 | // Main //
391 | //======//
392 | Habitat.install = (global) => {
393 |
394 | if (Habitat.installed) return
395 |
396 | if (!Habitat.Array.installed) Habitat.Array.install(global)
397 | if (!Habitat.Async.installed) Habitat.Async.install(global)
398 | if (!Habitat.Colour.installed) Habitat.Colour.install(global)
399 | if (!Habitat.Console.installed) Habitat.Console.install(global)
400 | if (!Habitat.Document.installed) Habitat.Document.install(global)
401 | if (!Habitat.Event.installed) Habitat.Event.install(global)
402 | if (!Habitat.HTML.installed) Habitat.HTML.install(global)
403 | if (!Habitat.JavaScript.installed) Habitat.JavaScript.install(global)
404 | if (!Habitat.Keyboard.installed) Habitat.Keyboard.install(global)
405 | if (!Habitat.Math.installed) Habitat.Math.install(global)
406 | if (!Habitat.Mouse.installed) Habitat.Mouse.install(global)
407 | if (!Habitat.Number.installed) Habitat.Number.install(global)
408 | if (!Habitat.Object.installed) Habitat.Object.install(global)
409 | if (!Habitat.Property.installed) Habitat.Property.install(global)
410 | if (!Habitat.Random.installed) Habitat.Random.install(global)
411 | if (!Habitat.Stage.installed) Habitat.Stage.install(global)
412 | if (!Habitat.String.installed) Habitat.String.install(global)
413 | if (!Habitat.Touches.installed) Habitat.Touches.install(global)
414 | if (!Habitat.Type.installed) Habitat.Type.install(global)
415 |
416 | Habitat.installed = true
417 |
418 | }
419 |
420 | //======//
421 | // Math //
422 | //======//
423 | {
424 |
425 | const gcd = (...numbers) => {
426 | const [head, ...tail] = numbers
427 | if (numbers.length === 1) return head
428 | if (numbers.length > 2) return gcd(head, gcd(...tail))
429 |
430 | let [a, b] = [head, ...tail]
431 |
432 | while (true) {
433 | if (b === 0) return a
434 | a = a % b
435 | if (a === 0) return b
436 | b = b % a
437 | }
438 |
439 | }
440 |
441 | const reduce = (...numbers) => {
442 | const divisor = gcd(...numbers)
443 | return numbers.map(n => n / divisor)
444 | }
445 |
446 | const install = (global) => {
447 | global.Math.gcd = Habitat.Math.gcd
448 | global.Math.reduce = Habitat.Math.reduce
449 | Habitat.Math.installed = true
450 | }
451 |
452 |
453 | Habitat.Math = {install, gcd, reduce}
454 |
455 | }
456 |
457 |
458 | //=======//
459 | // Mouse //
460 | //=======//
461 | {
462 |
463 | const Mouse = Habitat.Mouse = {
464 | position: [undefined, undefined],
465 | }
466 |
467 | const buttonMap = ["Left", "Middle", "Right", "Back", "Forward"]
468 |
469 | Reflect.defineProperty(Mouse, "install", {
470 | value(global) {
471 | global.Mouse = Mouse
472 | global.addEventListener("mousedown", e => {
473 | const buttonName = buttonMap[e.button]
474 | Mouse[buttonName] = true
475 | })
476 |
477 | global.addEventListener("mouseup", e => {
478 | const buttonName = buttonMap[e.button]
479 | Mouse[buttonName] = false
480 | })
481 |
482 | global.addEventListener("mousemove", e => {
483 | Mouse.position[0] = event.clientX
484 | Mouse.position[1] = event.clientY
485 | })
486 |
487 | Reflect.defineProperty(Mouse, "installed", {
488 | value: true,
489 | configurable: true,
490 | enumerable: false,
491 | writable: true,
492 | })
493 | },
494 | configurable: true,
495 | enumerable: false,
496 | writable: true,
497 | })
498 |
499 | }
500 |
501 |
502 | //========//
503 | // Number //
504 | //========//
505 | {
506 |
507 | const install = (global) => {
508 |
509 | Reflect.defineProperty(global.Number.prototype, "to", {
510 | value: function* (v) {
511 | let i = this.valueOf()
512 | if (i <= v) {
513 | while (i <= v) {
514 | yield i
515 | i++
516 | }
517 | }
518 | else {
519 | while (i >= v) {
520 | yield i
521 | i--
522 | }
523 | }
524 | },
525 | configurable: true,
526 | enumerable: false,
527 | writable: true,
528 | })
529 |
530 | const numberToString = global.Number.prototype.toString
531 | Reflect.defineProperty(global.Number.prototype, "toString", {
532 | value(base, size) {
533 | if (size === undefined) return numberToString.call(this, base)
534 | if (size <= 0) return ""
535 | const string = numberToString.call(this, base)
536 | return string.slice(-size).padStart(size, "0")
537 | },
538 | configurable: true,
539 | enumerable: false,
540 | writable: true,
541 | })
542 |
543 | if (global.BigInt !== undefined) {
544 | const bigIntToString = global.BigInt.prototype.toString
545 | Reflect.defineProperty(global.BigInt.prototype, "toString", {
546 | value(base, size) {
547 | if (size === undefined) return bigIntToString.call(this, base)
548 | if (size <= 0) return ""
549 | const string = bigIntToString.call(this, base)
550 | return string.slice(-size).padStart(size, "0")
551 | },
552 | configurable: true,
553 | enumerable: false,
554 | writable: true,
555 | })
556 | }
557 |
558 | Habitat.Number.installed = true
559 |
560 | }
561 |
562 | Habitat.Number = {install}
563 |
564 | }
565 |
566 | //========//
567 | // Object //
568 | //========//
569 | {
570 | Habitat.Object = {}
571 | Habitat.Object.install = (global) => {
572 |
573 | Reflect.defineProperty(global.Object.prototype, Symbol.iterator, {
574 | value: function*() {
575 | for (const key in this) {
576 | yield this[key]
577 | }
578 | },
579 | configurable: true,
580 | enumerable: false,
581 | writable: true,
582 | })
583 |
584 | Reflect.defineProperty(global.Object.prototype, "keys", {
585 | value() {
586 | return Object.keys(this)
587 | },
588 | configurable: true,
589 | enumerable: false,
590 | writable: true,
591 | })
592 |
593 | Reflect.defineProperty(global.Object.prototype, "values", {
594 | value() {
595 | return Object.values(this)
596 | },
597 | configurable: true,
598 | enumerable: false,
599 | writable: true,
600 | })
601 |
602 | Reflect.defineProperty(global.Object.prototype, "entries", {
603 | value() {
604 | return Object.entries(this)
605 | },
606 | configurable: true,
607 | enumerable: false,
608 | writable: true,
609 | })
610 |
611 | Habitat.Object.installed = true
612 |
613 | }
614 |
615 | }
616 |
617 | //==========//
618 | // Property //
619 | //==========//
620 | {
621 |
622 | const install = (global) => {
623 |
624 | Reflect.defineProperty(global.Object.prototype, "_", {
625 | get() {
626 | return new Proxy(this, {
627 | set(object, propertyName, descriptor) {
628 | Reflect.defineProperty(object, propertyName, descriptor)
629 | },
630 | get(object, propertyName) {
631 | const editor = {
632 | get value() {
633 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
634 | const {value} = descriptor
635 | return value
636 | },
637 | set value(value) {
638 | const {enumerable, configurable, writable} = Reflect.getOwnPropertyDescriptor(object, propertyName) || {enumerable: true, configurable: true, writable: true}
639 | const descriptor = {value, enumerable, configurable, writable}
640 | Reflect.defineProperty(object, propertyName, descriptor)
641 | },
642 | get get() {
643 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
644 | const {get} = descriptor
645 | return get
646 | },
647 | set get(get) {
648 | const {set, enumerable, configurable} = Reflect.getOwnPropertyDescriptor(object, propertyName) || {enumerable: true, configurable: true}
649 | const descriptor = {get, set, enumerable, configurable}
650 | Reflect.defineProperty(object, propertyName, descriptor)
651 | },
652 | get set() {
653 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
654 | const {set} = descriptor
655 | return set
656 | },
657 | set set(set) {
658 | const {get, enumerable, configurable} = Reflect.getOwnPropertyDescriptor(object, propertyName) || {enumerable: true, configurable: true}
659 | const descriptor = {get, set, enumerable, configurable}
660 | Reflect.defineProperty(object, propertyName, descriptor)
661 | },
662 | get enumerable() {
663 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
664 | const {enumerable} = descriptor
665 | return enumerable
666 | },
667 | set enumerable(v) {
668 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {configurable: true, writable: true}
669 | descriptor.enumerable = v
670 | Reflect.defineProperty(object, propertyName, descriptor)
671 | },
672 | get configurable() {
673 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
674 | const {configurable} = descriptor
675 | return configurable
676 | },
677 | set configurable(v) {
678 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {enumerable: true, writable: true}
679 | descriptor.configurable = v
680 | Reflect.defineProperty(object, propertyName, descriptor)
681 | },
682 | get writable() {
683 | const descriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {}
684 | const {writable} = descriptor
685 | return writable
686 | },
687 | set writable(v) {
688 | const oldDescriptor = Reflect.getOwnPropertyDescriptor(object, propertyName) || {enumerable: true, configurable: true}
689 | const {get, set, writable, ...rest} = oldDescriptor
690 | const newDescriptor = {...rest, writable: v}
691 | Reflect.defineProperty(object, propertyName, newDescriptor)
692 | },
693 | }
694 | return editor
695 | },
696 | })
697 | },
698 | set(value) {
699 | Reflect.defineProperty(this, "_", {value, configurable: true, writable: true, enumerable: true})
700 | },
701 | configurable: true,
702 | enumerable: false,
703 | })
704 |
705 |
706 | Habitat.Property.installed = true
707 |
708 | }
709 |
710 | Habitat.Property = {install}
711 |
712 | }
713 |
714 | //========//
715 | // Random //
716 | //========//
717 | {
718 | Habitat.Random = {}
719 |
720 | const maxId8 = 2 ** 16
721 | const u8s = new Uint8Array(maxId8)
722 | let id8 = maxId8
723 | const getRandomUint8 = () => {
724 |
725 | if (id8 >= maxId8) {
726 | crypto.getRandomValues(u8s)
727 | id8 = 0
728 | }
729 |
730 | const result = u8s[id8]
731 | id8++
732 | return result
733 | }
734 |
735 | Reflect.defineProperty(Habitat.Random, "Uint8", {
736 | get: getRandomUint8,
737 | configurable: true,
738 | enumerable: true,
739 | })
740 |
741 | const maxId32 = 2 ** 14
742 | const u32s = new Uint32Array(maxId32)
743 | let id32 = maxId32
744 | const getRandomUint32 = () => {
745 |
746 | if (id32 >= maxId32) {
747 | crypto.getRandomValues(u32s)
748 | id32 = 0
749 | }
750 |
751 | const result = u32s[id32]
752 | id32++
753 | return result
754 | }
755 |
756 | Reflect.defineProperty(Habitat.Random, "Uint32", {
757 | get: getRandomUint32,
758 | configurable: true,
759 | enumerable: true,
760 | })
761 |
762 | Habitat.Random.oneIn = (n) => {
763 | const result = getRandomUint32()
764 | return result % n === 0
765 | }
766 |
767 | Habitat.Random.maybe = (chance) => {
768 | return Habitat.Random.oneIn(1 / chance)
769 | }
770 |
771 | Habitat.Random.install = (global) => {
772 | global.Random = Habitat.Random
773 | global.oneIn = Habitat.Random.oneIn
774 | global.maybe = Habitat.Random.maybe
775 | Habitat.Random.installed = true
776 | }
777 |
778 | }
779 |
780 | //=======//
781 | // Stage //
782 | //=======//
783 | {
784 |
785 | Habitat.Stage = {}
786 | Habitat.Stage.make = () => {
787 |
788 | const canvas = document.createElement("canvas")
789 | const context = canvas.getContext("2d")
790 |
791 | const stage = {
792 | canvas,
793 | context,
794 | update: () => {},
795 | draw: () => {},
796 | tick: () => {
797 | stage.update()
798 | stage.draw()
799 | requestAnimationFrame(stage.tick)
800 | },
801 | }
802 |
803 | requestAnimationFrame(stage.tick)
804 | return stage
805 | }
806 |
807 | Habitat.Stage.install = (global) => {
808 | global.Stage = Habitat.Stage
809 | Habitat.Stage.installed = true
810 |
811 | }
812 |
813 | }
814 |
815 | //========//
816 | // String //
817 | //========//
818 | {
819 |
820 | const install = (global) => {
821 |
822 | Reflect.defineProperty(global.String.prototype, "divide", {
823 | value(n) {
824 | const regExp = RegExp(`[^]{1,${n}}`, "g")
825 | return this.match(regExp)
826 | },
827 | configurable: true,
828 | enumerable: false,
829 | writable: true,
830 | })
831 |
832 | Reflect.defineProperty(global.String.prototype, "toNumber", {
833 | value(base) {
834 | return parseInt(this, base)
835 | },
836 | configurable: true,
837 | enumerable: false,
838 | writable: true,
839 | })
840 |
841 | Habitat.String.installed = true
842 |
843 | }
844 |
845 | Habitat.String = {install}
846 |
847 | }
848 |
849 | //=======//
850 | // Touch //
851 | //=======//
852 | {
853 |
854 | const Touches = Habitat.Touches = []
855 |
856 | const trim = (a) => {
857 | if (a.length == 0) return a
858 | let start = a.length - 1
859 | let end = 0
860 | for (let i = 0; i < a.length; i++) {
861 | const value = a[i]
862 | if (value !== undefined) {
863 | start = i
864 | break
865 | }
866 | }
867 | for (let i = a.length - 1; i >= 0; i--) {
868 | const value = a[i]
869 | if (value !== undefined) {
870 | end = i + 1
871 | break
872 | }
873 | }
874 | a.splice(end)
875 | a.splice(0, start)
876 | return a
877 | }
878 |
879 | Reflect.defineProperty(Touches, "install", {
880 | value(global) {
881 |
882 | global.Touches = Touches
883 | global.addEventListener("touchstart", e => {
884 | for (const changedTouch of e.changedTouches) {
885 | const x = changedTouch.clientX
886 | const y = changedTouch.clientY
887 | const id = changedTouch.identifier
888 | if (Touches[id] === undefined) Touches[id] = {position: [undefined, undefined]}
889 | const touch = Touches[id]
890 | touch.position[0] = x
891 | touch.position[1] = y
892 | }
893 | })
894 |
895 | global.addEventListener("touchmove", e => {
896 | try {
897 | for (const changedTouch of e.changedTouches) {
898 | const x = changedTouch.clientX
899 | const y = changedTouch.clientY
900 | const id = changedTouch.identifier
901 | let touch = Touches[id]
902 | if (touch == undefined) {
903 | touch = {position: [undefined, undefined]}
904 | Touches[id] = touch
905 | }
906 |
907 | touch.position[0] = x
908 | touch.position[1] = y
909 | }
910 | }
911 | catch(e) {
912 | console.error(e)
913 | }
914 | })
915 |
916 | global.addEventListener("touchend", e => {
917 | for (const changedTouch of e.changedTouches) {
918 | const id = changedTouch.identifier
919 | Touches[id] = undefined
920 | }
921 | trim(Touches)
922 | })
923 |
924 | Reflect.defineProperty(Touches, "installed", {
925 | value: true,
926 | configurable: true,
927 | enumerable: false,
928 | writable: true,
929 | })
930 | },
931 | configurable: true,
932 | enumerable: false,
933 | writable: true,
934 | })
935 |
936 |
937 | }
938 |
939 |
940 | //======//
941 | // Type //
942 | //======//
943 | {
944 |
945 | const Int = {
946 | check: (n) => n % 1 == 0,
947 | convert: (n) => parseInt(n),
948 | }
949 |
950 | const Positive = {
951 | check: (n) => n >= 0,
952 | convert: (n) => Math.abs(n),
953 | }
954 |
955 | const Negative = {
956 | check: (n) => n <= 0,
957 | convert: (n) => -Math.abs(n),
958 | }
959 |
960 | const UInt = {
961 | check: (n) => n % 1 == 0 && n >= 0,
962 | convert: (n) => Math.abs(parseInt(n)),
963 | }
964 |
965 | const UpperCase = {
966 | check: (s) => s == s.toUpperCase(),
967 | convert: (s) => s.toUpperCase(),
968 | }
969 |
970 | const LowerCase = {
971 | check: (s) => s == s.toLowerCase(),
972 | convert: (s) => s.toLowerCase(),
973 | }
974 |
975 | const WhiteSpace = {
976 | check: (s) => /^[ | ]*$/.test(s),
977 | }
978 |
979 | const PureObject = {
980 | check: (o) => o.constructor == Object,
981 | }
982 |
983 | const Primitive = {
984 | check: p => p.is(Number) || p.is(String) || p.is(RegExp) || p.is(Symbol),
985 | }
986 |
987 | const install = (global) => {
988 |
989 | global.Int = Int
990 | global.Positive = Positive
991 | global.Negative = Negative
992 | global.UInt = UInt
993 | global.UpperCase = UpperCase
994 | global.LowerCase = LowerCase
995 | global.WhiteSpace = WhiteSpace
996 | global.PureObject = PureObject
997 | global.Primitive = Primitive
998 |
999 | Reflect.defineProperty(global.Object.prototype, "is", {
1000 | value(type) {
1001 | if ("check" in type) {
1002 | try { return type.check(this) }
1003 | catch {}
1004 | }
1005 | try { return this instanceof type }
1006 | catch { return false }
1007 | },
1008 | configurable: true,
1009 | enumerable: false,
1010 | writable: true,
1011 | })
1012 |
1013 | Reflect.defineProperty(global.Object.prototype, "as", {
1014 | value(type) {
1015 | if ("convert" in type) {
1016 | try { return type.convert(this) }
1017 | catch {}
1018 | }
1019 | return type(this)
1020 | },
1021 | configurable: true,
1022 | enumerable: false,
1023 | writable: true,
1024 | })
1025 |
1026 | Habitat.Type.installed = true
1027 |
1028 | }
1029 |
1030 | Habitat.Type = {install, Int, Positive, Negative, UInt, UpperCase, LowerCase, WhiteSpace, PureObject, Primitive}
1031 |
1032 | }
--------------------------------------------------------------------------------
/images/Black/Other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Black/Other.png
--------------------------------------------------------------------------------
/images/Black/Other.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Black/Other.psd
--------------------------------------------------------------------------------
/images/Black/Other@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Black/Other@0.25x.png
--------------------------------------------------------------------------------
/images/Black/Other@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Black/Other@0.5x.png
--------------------------------------------------------------------------------
/images/Black/Other@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Black/Other@0.75x.png
--------------------------------------------------------------------------------
/images/Blank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Blank.png
--------------------------------------------------------------------------------
/images/Blank.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Blank.psd
--------------------------------------------------------------------------------
/images/Blank@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Blank@0.25x.png
--------------------------------------------------------------------------------
/images/Blank@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Blank@0.5x.png
--------------------------------------------------------------------------------
/images/Blank@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Blank@0.75x.png
--------------------------------------------------------------------------------
/images/Cyan/Mad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Mad.png
--------------------------------------------------------------------------------
/images/Cyan/Mad.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Mad.psd
--------------------------------------------------------------------------------
/images/Cyan/Mad@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Mad@0.25x.png
--------------------------------------------------------------------------------
/images/Cyan/Mad@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Mad@0.5x.png
--------------------------------------------------------------------------------
/images/Cyan/Mad@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Mad@0.75x.png
--------------------------------------------------------------------------------
/images/Cyan/Other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Other.png
--------------------------------------------------------------------------------
/images/Cyan/Other.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Other.psd
--------------------------------------------------------------------------------
/images/Cyan/Other@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Other@0.25x.png
--------------------------------------------------------------------------------
/images/Cyan/Other@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Other@0.5x.png
--------------------------------------------------------------------------------
/images/Cyan/Other@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Cyan/Other@0.75x.png
--------------------------------------------------------------------------------
/images/Green/Other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Green/Other.png
--------------------------------------------------------------------------------
/images/Green/Other.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Green/Other.psd
--------------------------------------------------------------------------------
/images/Green/Other@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Green/Other@0.25x.png
--------------------------------------------------------------------------------
/images/Green/Other@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Green/Other@0.5x.png
--------------------------------------------------------------------------------
/images/Green/Other@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Green/Other@0.75x.png
--------------------------------------------------------------------------------
/images/Purple/Other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Purple/Other.png
--------------------------------------------------------------------------------
/images/Purple/Other.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Purple/Other.psd
--------------------------------------------------------------------------------
/images/Purple/Other@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Purple/Other@0.25x.png
--------------------------------------------------------------------------------
/images/Purple/Other@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Purple/Other@0.5x.png
--------------------------------------------------------------------------------
/images/Purple/Other@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Purple/Other@0.75x.png
--------------------------------------------------------------------------------
/images/Yellow/Other.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Yellow/Other.png
--------------------------------------------------------------------------------
/images/Yellow/Other.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Yellow/Other.psd
--------------------------------------------------------------------------------
/images/Yellow/Other@0.25x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Yellow/Other@0.25x.png
--------------------------------------------------------------------------------
/images/Yellow/Other@0.5x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Yellow/Other@0.5x.png
--------------------------------------------------------------------------------
/images/Yellow/Other@0.75x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TodePond/TimePond/9767e33c8fc44bf56db826b9219ca721fdd734e3/images/Yellow/Other@0.75x.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/source/atom.js:
--------------------------------------------------------------------------------
1 | //=======//
2 | // Setup //
3 | //=======//
4 | let ATOM_ID = 0
5 | const makeAtom = ({
6 | width = 50,
7 | height = 50,
8 | x = WORLD_WIDTH/2 - width/2,
9 | y = WORLD_HEIGHT/2 - height/2,
10 | dx = 0,
11 | dy = 0,
12 | draw = DRAW_RECTANGLE,
13 | update = UPDATE_STATIC,
14 | grab = GRAB_DRAG,
15 | turns = 0,
16 | cutTop = 0,
17 | cutBottom = 0,
18 | cutRight = 0,
19 | cutLeft = 0,
20 | autoLinks = [],
21 | construct = () => {},
22 | flipX = false,
23 | ...args
24 | } = {}, {autoTurn = true} = {}) => {
25 | const atom = {
26 | id: ATOM_ID++,
27 | width,
28 | height,
29 | cutTop,
30 | cutBottom,
31 | cutRight,
32 | cutLeft,
33 | x,
34 | y,
35 | dx,
36 | dy,
37 | nextdx: dx,
38 | nextdy: dy,
39 | turns: 0,
40 | nextturns: 0,
41 | draw,
42 | update,
43 | grab,
44 | flipX: false,
45 | portals: {top: undefined, bottom: undefined, left: undefined, right: undefined},
46 | links: [],
47 | ...args
48 | }
49 |
50 | if (flipX) {
51 | flipAtom(atom)
52 | }
53 |
54 | for (const autoLink of autoLinks) {
55 | const latom = makeAtom(autoLink.element)
56 | linkAtom(atom, latom, autoLink.offset, autoLink.transfer)
57 | }
58 |
59 | // Band-aid for old silly arguments idea
60 | if (autoTurn) {
61 | turnAtom(atom, turns)
62 | }
63 | else {
64 | // I SHOULD do this
65 | // But at this point, I've hardcoded fixes for it everywhere else in the code
66 | // so I can't do it now
67 | // fix it in TimePond 2
68 | //atom.turns = turns
69 | }
70 |
71 | construct(atom)
72 | return atom
73 | }
74 |
75 | const cloneAtom = (atom) => {
76 | const clone = {}
77 | for (const key in atom) {
78 | clone[key] = deepishCloneAtomProperty(atom[key], key)
79 | }
80 | return makeAtom(clone, {autoTurn: false})
81 | }
82 |
83 |
84 |
85 | const deepishCloneAtomProperty = (value, key) => {
86 | if (key === "id") return ATOM_ID++
87 | if (key === "portals") return {top: undefined, bottom: undefined, left: undefined, right: undefined}
88 | if (key === "links") return []
89 | if (typeof value === "undefined") return value
90 | if (typeof value === "string") return value
91 | if (typeof value === "number") return value
92 | if (typeof value === "boolean") return value
93 | if (typeof value === "function") return value //not deepcloning but i promise i wont mess around with function properties
94 | if (typeof value === "object") {
95 | /*if (value instanceof Array) {
96 | const array = []
97 | for (const key in value) {
98 | array[key] = value[key] //not a pure deep clone
99 | }
100 | return array.d
101 | }*/
102 | if (value instanceof Object) {
103 | const object = {}
104 | for (const key in value) {
105 | object[key] = value[key] //not a pure deep clone either cos we want the atom REFERENCES yo
106 | }
107 | return object
108 | }
109 | }
110 | console.error("Couldn't deepish-clone value", value)
111 | }
112 |
113 | //===========//
114 | // Game Loop //
115 | //===========//
116 | const updateAtom = (atom, world) => {
117 | if (atom.skipUpdate === true) {
118 | atom.skipUpdate = false
119 | }
120 | else {
121 |
122 | //atom.prevBounds = getBounds(atom)
123 | atom.update(atom, world)
124 | }
125 | updateAtomLinks(atom, world)
126 | }
127 |
128 | const updateAtomLinks = (atom) => {
129 | for (const link of atom.links) {
130 | //link.atom.prevBounds = getBounds(link.atom)
131 | for (const key of LINKED_PROPERTIES) {
132 |
133 | if (link.offset[key] !== undefined) {
134 | const them = atom[key]
135 | const me = link.atom[key]
136 | link.atom[key] = link.offset[key](them, me)
137 | }
138 | else {
139 | link.atom[key] = atom[key]
140 | }
141 |
142 |
143 | }
144 |
145 | updateAtomLinks(link.atom)
146 | }
147 | }
148 |
149 | const drawAtom = (atom, context) => {
150 | const {draw} = atom
151 | draw(atom, context)
152 | }
153 |
154 | //=========//
155 | // Usefuls //
156 | //=========//
157 | const linkAtom = (atom, latom, offset={}, transfer={}) => {
158 | if (atom === undefined) return
159 | const trans = {...transfer}
160 | for (const key of LINKED_PROPERTIES) {
161 | if (trans[key] === undefined) {
162 | trans[key] = (parent, child, key, value=child[key]) => parent[key] = value
163 | }
164 | }
165 | const link = {atom: latom, offset: {...offset}, transfer: trans}
166 | atom.links.push(link)
167 | latom.parent = atom
168 | return link
169 | }
170 |
171 | const getDescendentsAndMe = (self) => {
172 | if (self.links.length === 0) return [self]
173 | const atoms = self.links.map(link => link.atom)
174 | const descendents = atoms.map(atom => getDescendentsAndMe(atom)).flat(1)
175 | return [self, ...descendents]
176 | }
177 |
178 | const transferToParent = (child, key, value) => {
179 | const parent = child.parent
180 | if (parent === undefined) return
181 | const link = getLink(child)
182 | if (link === undefined) {
183 | print("orphan - no parent to transfer to")
184 | return
185 | }
186 | link.transfer[key](parent, child, key, value)
187 | if (parent.parent !== undefined) transferToParent(parent, key, parent[key])
188 | }
189 |
190 | const getLink = (child) => {
191 | const parent = child.parent
192 | if (parent === undefined) return
193 | for (const link of parent.links) {
194 | if (link.atom === child) return link
195 | }
196 | //print(child.world.id)
197 | //print(parent.world.id)
198 | print("orphan in world", child.world.id)
199 | //removeAtom(child.world, child, {includingChildren: true, destroy: true})
200 | //child.cutRight = 0
201 | //throw new Error(`[TimePond] There's an orphan! This shouldn't happen... I haven't made sure that all children have parent links properly :(`)
202 |
203 | }
204 |
205 | const moveAtom = (atom, x, y) => {
206 | atom.x = x
207 | atom.y = y
208 | updateAtomLinks(atom)
209 | }
210 |
211 | const flipAtom = (atom) => {
212 | atom.flipX = !atom.flipX
213 | const [newCutLeft, newCutRight] = [atom.cutRight, atom.cutLeft]
214 |
215 | atom.cutLeft = newCutLeft
216 | atom.cutRight = newCutRight
217 | const cutDiff = newCutLeft - newCutRight
218 | atom.x -= cutDiff
219 |
220 | //atom.cutBottom.d
221 | /*print("")
222 | print("FLIPPED")
223 | print("cutBottom", atom.cutBottom)
224 | print("cutTop", atom.cutTop)
225 | print("cutLeft", atom.cutLeft)
226 | print("cutRight", atom.cutRight)*/
227 | }
228 |
229 | const turnAtom = (atom, turns=1, fallSafe=false, rejectIfOverlap=false, world, exceptions=[], overridePortals=false) => {
230 | if (!overridePortals) {
231 | if (atom.portals.top !== undefined) return false
232 | if (atom.portals.bottom !== undefined) return false
233 | if (atom.portals.right !== undefined) return false
234 | if (atom.portals.left !== undefined) return false
235 | }
236 | if (atom.turns === undefined) atom.turns = 0
237 | if (turns === 0) return true
238 | if (turns < 0) return turnAtom(atom, 4+turns, fallSafe, rejectIfOverlap, world, exceptions=[], overridePortals)
239 | if (turns > 1) {
240 | const result = turnAtom(atom, 1, fallSafe, rejectIfOverlap, world, exceptions=[], overridePortals)
241 | if (!result) return false
242 | return turnAtom(atom, turns-1, fallSafe, rejectIfOverlap, world, exceptions=[], overridePortals)
243 | }
244 | const old = {}
245 | const obounds = getBounds(atom)
246 | const {height, width, cutTop, cutBottom, cutRight, cutLeft} = atom
247 | old.height = height
248 | old.width = width
249 | old.cutTop = cutTop
250 | old.cutBottom = cutBottom
251 | old.cutLeft = cutLeft
252 | old.cutRight = cutRight
253 |
254 | old.y = atom.y
255 | old.x = atom.x
256 | atom.height = width
257 | atom.width = height
258 |
259 | if (!atom.flipX) {
260 | atom.cutBottom = cutRight
261 | atom.cutLeft = cutBottom
262 | atom.cutTop = cutLeft
263 | atom.cutRight = cutTop
264 | }
265 | else {
266 | atom.cutBottom = cutLeft
267 | atom.cutLeft = cutTop
268 | atom.cutTop = cutRight
269 | atom.cutRight = cutBottom
270 | }
271 |
272 | const oldLinks = new Map()
273 | for (const link of atom.links) {
274 | const oldLink = {}
275 | oldLinks.set(link, oldLink)
276 | oldLink.height = link.atom.height
277 | oldLink.width = link.atom.width
278 | oldLink.cutTop = link.atom.cutTop
279 | oldLink.cutBottom = link.atom.cutBottom
280 | oldLink.cutLeft = link.atom.cutLeft
281 | oldLink.cutRight = link.atom.cutRight
282 |
283 | oldLink.y = link.atom.y
284 | oldLink.x = link.atom.x
285 |
286 | oldLink.offset = {}
287 | oldLink.transfer = {}
288 |
289 | for (const key of LINKED_PROPERTIES) {
290 | oldLink.offset[key] = link.offset[key]
291 | oldLink.transfer[key] = link.transfer[key]
292 | }
293 |
294 | for (const linkType of ["offset", "transfer"]) {
295 | link[linkType].width = oldLink[linkType].height
296 | link[linkType].height = oldLink[linkType].width
297 |
298 | if (!link.atom.flipX) {
299 | link[linkType].cutBottom = oldLink[linkType].cutRight
300 | link[linkType].cutLeft = oldLink[linkType].cutBottom
301 | link[linkType].cutTop = oldLink[linkType].cutLeft
302 | link[linkType].cutRight = oldLink[linkType].cutTop
303 | }
304 | else {
305 | link[linkType].cutBottom = oldLink[linkType].cutLeft
306 | link[linkType].cutLeft = oldLink[linkType].cutTop
307 | link[linkType].cutTop = oldLink[linkType].cutRight
308 | link[linkType].cutRight = oldLink[linkType].cutBottom
309 | }
310 |
311 | link[linkType].x = oldLink[linkType].y
312 | link[linkType].y = oldLink[linkType].x
313 | link[linkType].dx = oldLink[linkType].dy
314 | link[linkType].dy = oldLink[linkType].dx
315 | link[linkType].nextdx = oldLink[linkType].nextdy
316 | link[linkType].nextdy = oldLink[linkType].nextdx
317 | }
318 |
319 | turnAtom(link.atom, turns, fallSafe, false, world, exceptions, overridePortals)
320 |
321 | }
322 |
323 | if (rejectIfOverlap) {
324 |
325 | const nbounds = getBounds(atom)
326 | atom.y -= nbounds.bottom-obounds.bottom + 1
327 | atom.x -= (atom.width-atom.height)/2
328 | for (const a of world.atoms) {
329 | if (a === atom) continue
330 | if (exceptions.includes(a)) continue
331 | if (atomOverlaps(atom, a)) {
332 | //const bounds = getBounds(atom)
333 | //const abounds = getBounds(a)
334 | //if (abounds.top === bounds.bottom) continue
335 | for (const key in old) {
336 | atom[key] = old[key]
337 | }
338 | for (const [link, oldLink] of oldLinks) {
339 | for (const key in oldLink) {
340 | if (key === "offset" || key === "transfer") continue
341 | link.atom[key] = oldLink[key]
342 | }
343 | for (const key in oldLink.offset) {
344 | link.offset[key] = oldLink.offset[key]
345 | }
346 | for (const key in oldLink.transfer) {
347 | link.transfer[key] = oldLink.transfer[key]
348 | }
349 | }
350 | return false
351 | }
352 | }
353 |
354 | }
355 | atom.turns++
356 | if (atom.turns >= 4) atom.turns = 0
357 |
358 |
359 | /*print("")
360 | print("TURNED")
361 | print("cutBottom", atom.cutBottom)
362 | print("cutTop", atom.cutTop)
363 | print("cutLeft", atom.cutLeft)
364 | print("cutRight", atom.cutRight)*/
365 |
366 | return true
367 | }
368 |
369 | const getBounds = ({x, y, width, height, cutTop=0, cutBottom=0, cutLeft=0, cutRight=0}) => {
370 | const top = y + cutTop
371 | const bottom = y + height - cutBottom
372 | const left = x + cutLeft
373 | const right = x + width - cutRight
374 | return {top, bottom, left, right}
375 | }
376 |
377 | const pointOverlaps = ({x, y}, atom) => {
378 | const {left, right, top, bottom} = getBounds(atom)
379 | return x >= left && x <= right && y >= top && y <= bottom
380 | }
381 |
382 | const getAtomAncestor = (atom) => {
383 | if (atom.parent === undefined) return atom
384 | return getAtomAncestor(atom.parent)
385 | }
386 |
387 | const atomIsDescendant = (kid, parent) => {
388 | if (kid.parent === parent) return true
389 | if (kid.parent === undefined) return false
390 | return atomIsDescendant(kid.parent, parent)
391 | }
392 |
393 |
394 | const atomOverlaps = (self, atom) => {
395 |
396 | if (atomIsDescendant(self, atom)) return false
397 | if (atomIsDescendant(atom, self)) return false
398 |
399 | for (const link of self.links) {
400 | const result = atomOverlaps(link.atom, atom)
401 | if (result) return true
402 | }
403 |
404 | const bounds = getBounds(self)
405 | const abounds = getBounds(atom)
406 |
407 | const horizAligns = aligns([bounds.left, bounds.right], [], [abounds.left, abounds.right])
408 | const vertAligns = aligns([bounds.top, bounds.bottom], [], [abounds.top, abounds.bottom])
409 | if (horizAligns && vertAligns) return true
410 | //if (horizAligns && bounds.top <= abounds.top && bounds.bottom >= abounds.bottom) return true
411 | //if (horizAligns && bounds.left <= abounds.left && bounds.right >= abounds.right) return true
412 |
413 | const ahorizAligns = aligns([abounds.left, abounds.right], [], [bounds.left, bounds.right])
414 | const avertAligns = aligns([abounds.top, abounds.bottom], [], [bounds.top, bounds.bottom])
415 | if (ahorizAligns && avertAligns) return true
416 | //if (ahorizAligns && abounds.top <= bounds.top && abounds.bottom >= bounds.bottom) return true
417 | //if (avertAligns && abounds.left <= bounds.left && abounds.right >= bounds.right) return true
418 |
419 | return false
420 | }
421 |
422 | const getPointSide = (point, [left, right]) => {
423 | if (point < left) return -1
424 | if (point > right) return 1
425 | return 0
426 | }
427 |
428 | const aligns = ([left, right], [nleft, nright], [aleft, aright=aleft]) => {
429 | const leftSide = getPointSide(left, [aleft, aright])
430 | const rightSide = getPointSide(right, [aleft, aright])
431 | if (leftSide === 0) return true
432 | if (rightSide === 0) return true
433 | if (leftSide*-1 == rightSide) return true
434 |
435 | // For moving things
436 | if (nleft !== undefined && nright !== undefined) {
437 | const nleftSide = getPointSide(nleft, [aleft, aright])
438 | const nrightSide = getPointSide(nright, [aleft, aright])
439 | if (nleftSide === 0) return true
440 | if (nrightSide === 0) return true
441 | if (leftSide*-1 == nleftSide) return true
442 | if (rightSide*-1 == nrightSide) return true
443 | }
444 |
445 | return false
446 | }
--------------------------------------------------------------------------------
/source/config.js:
--------------------------------------------------------------------------------
1 | //========//
2 | // Config //
3 | //========//
4 | const WORLD_WIDTH = 500
5 | const WORLD_HEIGHT = 500
6 | let MENU_HEIGHT = 100
--------------------------------------------------------------------------------
/source/element.js:
--------------------------------------------------------------------------------
1 | //=========//
2 | // Drawers //
3 | //=========//
4 | const DRAW_RECTANGLE = (self, context) => {
5 | const {x, y, width, height, colour = Colour.Red, cutTop=0, cutBottom=0, cutLeft=0, cutRight=0} = self
6 | context.fillStyle = colour
7 | context.fillRect(x+cutLeft, y+cutTop, width-(cutRight+cutLeft), height-(cutBottom+cutTop))
8 | }
9 |
10 | const DRAW_CIRCLE = (self, context) => {
11 | const {x, y, width, height, colour = Colour.Red, cutTop=0, cutBottom=0, cutLeft=0, cutRight=0} = self
12 | context.fillStyle = colour
13 | context.beginPath()
14 | context.arc(x+height/2, y+height/2, height/2, 0, 2*Math.PI)
15 | context.fill()
16 | }
17 |
18 | const images = {}
19 | const DRAW_IMAGE = (self, context) => {
20 |
21 | if (self === undefined) return
22 |
23 | /*if (self.previousDraw !== undefined) {
24 | let prev = self.previousDraw
25 | DRAW_IMAGE(prev, context)
26 | //self.trailCount--
27 |
28 | }
29 |
30 | if (self.trailCount === undefined || self.trailCount < TRAIL_LENGTH) {
31 | self.previousDraw = cloneAtom(self)
32 | self.previousDraw.trailCount = self.trailCount + 1
33 | }
34 | else {
35 | self.previousDraw = undefined
36 | }*/
37 |
38 | //self.previousDraw = cloneAtom(self)
39 | //self.previousDraw.previousDraw = undefined
40 |
41 | // Sprite
42 | if (images[self.source] === undefined) {
43 | const image = new Image()
44 | image.src = self.source
45 | images[self.source] = image
46 | }
47 | const image = images[self.source]
48 | const imageHeightRatio = image.height/self.height
49 | const imageWidthRatio = image.width/self.width
50 |
51 | // Positioning
52 | const bounds = getBounds(self)
53 | const boundsWidth = bounds.right - bounds.left
54 | const boundsHeight = bounds.bottom - bounds.top
55 | const boundsDimensionDiff = boundsWidth - boundsHeight
56 |
57 | let centerX = (bounds.right + bounds.left)/2
58 | let centerY = (bounds.bottom + bounds.top)/2
59 | const centerDiff = centerX - centerY
60 |
61 | if (self.turns === 1) {
62 | //centerY += boundsDimensionDiff/2
63 | }
64 | /* else if (self.turns === 3) {
65 | centerY -= boundsDimensionDiff/2
66 | centerX -= boundsDimensionDiff/4
67 | }*/
68 |
69 | // Cuts
70 | let {cutRight, cutLeft, cutBottom, cutTop} = self
71 | if (!self.flipX) {
72 | for (let i = 0; i < self.turns; i++) [cutRight, cutBottom, cutLeft, cutTop] = [cutBottom, cutLeft, cutTop, cutRight]
73 | }
74 | if (self.flipX) {
75 | //;[cutLeft, cutRight] = [cutRight, cutLeft]
76 | if (self.turns % 2 !== 0) [cutLeft, cutRight] = [cutRight, cutLeft]
77 | if (self.turns % 2 === 0) [cutLeft, cutRight] = [cutRight, cutLeft]
78 | //if (self.turns % 2 !== 0) [cutTop, cutBottom] = [cutBottom, cutTop]
79 | for (let i = 0; i < self.turns; i++) [cutRight, cutBottom, cutLeft, cutTop] = [cutBottom, cutLeft, cutTop, cutRight]
80 | }
81 | //for (let i = 0; i < self.turns; i++) [cutRight, cutBottom, cutLeft, cutTop] = [cutBottom, cutLeft, cutTop, cutRight]
82 |
83 | //else if (self.flipX && self.turns % 2 === 0) [cutTop, cutBottom] = [cutBottom, cutTop]
84 |
85 | const cutWidth = cutRight + cutLeft
86 | const cutHeight = cutBottom + cutTop
87 |
88 | // Snippet
89 | const snippetX = cutLeft*imageWidthRatio
90 | const snippetY = cutTop*imageHeightRatio
91 | const snippetWidth = image.width - cutWidth*imageWidthRatio
92 | const snippetHeight = image.height - cutHeight*imageHeightRatio
93 |
94 | const flipWidthRatio = image.height/self.width
95 | const flipHeightRatio = image.width/self.height
96 |
97 |
98 | // Flips and Rotations
99 | context.save()
100 | if (self.flipX || self.turns > 0) {
101 | context.translate(centerX, centerY)
102 | if (self.flipX) context.scale(-1, 1)
103 | if (self.turns > 0) context.rotate(Math.PI/2 * self.turns)
104 | context.translate(-centerX, -centerY)
105 | }
106 |
107 | // Draw!
108 | /*if (self.turns % 2 !== 0) {
109 | //context.drawImage(image, snippetX, snippetY, snippetWidth, snippetHeight, bounds.left+boundsDimensionDiff/2, bounds.top-boundsDimensionDiff/2, boundsHeight, boundsWidth)
110 | cutLeft.d
111 | context.drawImage(image, snippetX, snippetY, snippetWidth+boundsDimensionDiff, snippetHeight-boundsDimensionDiff, bounds.left+boundsDimensionDiff/2, bounds.top-boundsDimensionDiff/2, boundsHeight, boundsWidth)
112 | }
113 | else {
114 | context.drawImage(image, snippetX, snippetY, snippetWidth, snippetHeight, bounds.left, bounds.top, boundsWidth, boundsHeight)
115 | }*/
116 |
117 | const alpha = self.opacity !== undefined? self.opacity : 1.0
118 | context.globalAlpha = alpha
119 | //print(self.opacity)
120 |
121 | if (self.turns % 2 !== 0) {
122 | context.drawImage(image, cutLeft*flipWidthRatio, cutTop*flipHeightRatio, image.width - cutLeft*flipWidthRatio - cutRight*flipWidthRatio, image.height - cutTop*flipHeightRatio - cutBottom*flipHeightRatio, bounds.left + boundsDimensionDiff/2, bounds.top - boundsDimensionDiff/2, boundsHeight, boundsWidth)
123 | }
124 | else {
125 | context.drawImage(image, snippetX, snippetY, snippetWidth, snippetHeight, bounds.left, bounds.top, boundsWidth, boundsHeight)
126 | }
127 |
128 | //context.drawImage(image, snippetX, snippetY, snippetWidth, snippetHeight, bounds.left, bounds.top, boundsWidth, boundsHeight)
129 | context.restore()
130 |
131 | // Debug: Showing bounding box!
132 | if (self.showBounds) {
133 | context.strokeStyle = Colour.White
134 | context.strokeRect(bounds.left, bounds.top, bounds.right-bounds.left, bounds.bottom-bounds.top)
135 | }
136 | }
137 |
138 | const DRAW_SPAWNER = (self, context) => {
139 | const {spawn} = self
140 | const {draw} = spawn
141 | draw(self, context)
142 | }
143 |
144 | //==========//
145 | // Updaters //
146 | //==========//
147 | const UPDATE_STATIC = (self) => {
148 | self.dx = 0
149 | self.dy = 0
150 | self.nextdx = 0
151 | self.nextdy = 0
152 | }
153 |
154 | const UPDATE_NONE = () => {}
155 |
156 | const UPDATE_MOVER_GRAVITY = 0.5
157 | const UPDATE_MOVER_AIR_RESISTANCE = 0.99
158 | const UPDATE_MOVER_FRICTION = 0.8
159 | const UPDATE_MOVER_BEING = (self, world) => {
160 | UPDATE_MOVER(self, world)
161 | if (self.flipX && self.dx < -0.1) {
162 | flipAtom(self)
163 | }
164 | else if (!self.flipX && self.dx > 0.1) {
165 | flipAtom(self)
166 | }
167 | if (self.nextdx < 0.1 && self.nextdx > -0.1 && self.grounded) {
168 | if (self.jumpTick > 60) {
169 | if (self.turns !== 0) {
170 | turnAtom(self, -self.turns, true, true, world)
171 | self.nextdx = 1 * (self.flipX? 1 : -1)
172 | self.nextdy = -2
173 | self.jumpTick = 0
174 | }
175 | else {
176 | self.nextdx = 3 * (self.flipX? 1 : -1)
177 | self.nextdy = -10
178 | self.jumpTick = 0
179 | }
180 | }
181 | else {
182 | if (self.jumpTick === undefined) self.jumpTick = 0
183 | self.jumpTick++
184 | }
185 | }
186 | }
187 |
188 | const Capitalised = {
189 | convert: (s) => s[0].as(UpperCase) + s.slice(1)
190 | }
191 |
192 | const makeBlink = () => ({})
193 | const UPDATE_MOVER = (self, world) => {
194 | return moverUpdate(self, world)
195 | }
196 |
197 | const getOppositeSideName = (side) => {
198 | if (side === "top") return "bottom"
199 | if (side === "bottom") return "top"
200 | if (side === "left") return "right"
201 | if (side === "right") return "left"
202 | }
203 |
204 | //==========//
205 | // Grabbers //
206 | //==========//
207 | const GRAB_DRAG = (self) => {
208 | if (self.portals !== undefined) {
209 | if (self.portals.top !== undefined) return undefined
210 | if (self.portals.bottom !== undefined) return undefined
211 | if (self.portals.left !== undefined) return undefined
212 | if (self.portals.right !== undefined) return undefined
213 | }
214 | if (self.isPortal) {
215 | for (const atom of self.world.atoms) {
216 | if (atom.portals !== undefined) {
217 | if (atom.portals.top === self) return undefined
218 | if (atom.portals.bottom === self) return undefined
219 | if (atom.portals.left === self) return undefined
220 | if (atom.portals.right === self) return undefined
221 | }
222 | }
223 | }
224 | return self
225 | }
226 | const GRAB_STATIC = () => {}
227 | const GRAB_SPAWNER = (self, hand, world) => {
228 | const atom = makeAtom(self.spawn)
229 | addAtom(world, atom)
230 | atom.x = self.x
231 | atom.y = self.y
232 | return atom
233 | }
234 |
235 | const GRAB_SPAWNER_PORTAL = (self, hand, world) => {
236 | //if (self.tally === undefined) self.tally = 0
237 | const grabbed = GRAB_SPAWNER(self, hand, world)
238 | /*self.tally++
239 | if (self.tally % 2 === 0) {
240 | if (self.tally/2 >= ELEMENT_PORTAL_COLOURS.length) self.tally = 0
241 | self.spawn = {...self.spawn, colour: ELEMENT_PORTAL_COLOURS[self.tally/2]}
242 | self.colour = self.spawn.colour
243 | }*/
244 | return grabbed
245 | }
246 |
247 | const GRAB_LINKEE = (self, hand, world) => {
248 | hand.offset.x -= self.x - self.parent.x
249 | hand.offset.y -= self.y - self.parent.y
250 | return self.parent.grab(self.parent, hand, world)
251 | }
252 |
253 | //=========//
254 | // Portals //
255 | //=========//
256 | const PORTAL_VOID = {
257 | enter: ({froggy}) => {
258 |
259 | },
260 | exit: (atom, world) => {
261 | //print("End voidal!")
262 | },
263 | moveIn: (atom, world) => {
264 | //print("Move in voidal!")
265 | },
266 | moveOut: (atom, world) => {
267 | //print("Move out voidal!")
268 | },
269 | move: () => {
270 | //print("Move through voidal!")
271 | }
272 | }
273 |
274 | const PORTAL_BOUNCE = {
275 | enter: (event) => {
276 |
277 | const realWorld = event.world.realWorld
278 | if (event.world.bounceTimer !== undefined) {
279 | event.froggy.nextdy *= -0.9
280 | //event.world.bounceTimer = undefined
281 | return
282 | }
283 |
284 | if (event.world.isCrashTest) {
285 | if (event.froggy.id === event.world.crashNeededFroggy) {
286 | event.world.crashSuccess = true
287 | return "CRASH"
288 | }
289 | }
290 |
291 | //print(event.world.id)
292 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
293 | //print("proj")
294 | }
295 | else {
296 | //print("bye")
297 | PORTAL_VOID.enter(event)
298 | return
299 | }
300 |
301 | if (realWorld.isProjection) {
302 | PORTAL_VOID.enter(event)
303 | return
304 | }
305 |
306 | const crashTestRealWorld = cloneWorld(event.world)
307 | const crashTestWorld = cloneWorld(realWorld)
308 | crashTestWorld.realWorld = crashTestRealWorld
309 | crashTestWorld.future_projection_skip = 30
310 |
311 | crashTestRealWorld.isCrashTest = true
312 | crashTestWorld.isCrashTest = true
313 |
314 | const crash_froggy = crashTestRealWorld.atoms.find(a => a.id === event.froggy.id)
315 | const crash_portal = crashTestWorld.atoms.find(a => a.id === event.portal.id)
316 | const crash_target = crash_portal.target
317 |
318 | crashTestRealWorld.crashNeededFroggy = event.froggy.id
319 | crashTestWorld.crashNeededFroggy = event.froggy.id
320 |
321 | PORTAL_MOVE.enter({portal:crash_portal, froggy: crash_froggy, axis: event.axis}, {target: crash_target})
322 | for (let i = 0; i < 31; i++) {
323 | fullUpdateWorld(crashTestRealWorld)
324 | fullUpdateWorld(crashTestWorld)
325 | }
326 |
327 | if (!crashTestWorld.crashSuccess) {
328 | print("PARADOX")
329 | realWorld.bounceTimer = 30
330 | //realWorld.future_projection_skip = 30
331 | //saveFutureProjection(realWorld)
332 | return
333 | }
334 |
335 |
336 | //realWorld.futureProjection = undefined
337 | //saveFutureProjection(realWorld)
338 | const clone_world = cloneWorld(realWorld)
339 | clone_world.future_projection_skip = 30
340 |
341 | addWorld(multiverse, clone_world)
342 |
343 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
344 | const clone_target = clone_portal.target
345 | PORTAL_MOVE.enter(event, {target: clone_target})
346 | print("nowline from", clone_portal, "to", clone_target)
347 |
348 | replaceWorld(realWorld, clone_world)
349 | realWorld.pruneTimer = 30
350 |
351 | return
352 |
353 | }
354 | }
355 |
356 | const PORTAL_FADE = {
357 | enter: (event) => {
358 |
359 | //print(event.world.id)
360 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
361 | //print("proj")
362 | }
363 | else {
364 | //print("bye")
365 | PORTAL_VOID.enter(event)
366 | return
367 | }
368 |
369 | const realWorld = event.world.realWorld
370 | if (realWorld.isProjection) {
371 | PORTAL_VOID.enter(event)
372 | return
373 | }
374 |
375 | //realWorld.futureProjection = undefined
376 | //saveFutureProjection(realWorld)
377 | const clone_world = cloneWorld(realWorld)
378 | //clone_world.futureProjection = undefined
379 | clone_world.future_projection_skip = 30
380 | //clone_world.projection_skip = 1
381 | //clone_world.isProjection = false
382 |
383 | addWorld(multiverse, clone_world)
384 |
385 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
386 | const clone_target = clone_portal.target
387 | const variant = PORTAL_MOVE.enter(event, {target: clone_target})
388 | const clone_froggy = clone_world.atoms.find(a => a.id === event.froggy.id)
389 | print("nowline from", clone_portal, "to", clone_target)
390 |
391 | replaceWorld(realWorld, clone_world)
392 | realWorld.pruneTimer = 30
393 |
394 | variant.fadeReliantOn = clone_froggy
395 | //variant.fadeReliantOn = clone_froggy
396 | //clone_froggy.fadeReliantOn = variant
397 | clone_world.fadeReliance = 30
398 |
399 | return
400 |
401 | }
402 | }
403 |
404 | const PORTAL_FREEZE = {
405 | enter: (event) => {
406 |
407 | //print(event.world.id)
408 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
409 | //print("proj")
410 | }
411 | else {
412 | //print("bye")
413 | PORTAL_VOID.enter(event)
414 | //event.froggy.fadeReliantOn = undefined
415 | if (event.froggy.fadeRelier !== undefined) event.froggy.fadeRelier.d.fadeReliantOn = undefined
416 | event.world.fadeReliance = undefined
417 | return
418 | }
419 |
420 | const realWorld = event.world.realWorld
421 | if (realWorld.isProjection) {
422 | PORTAL_VOID.enter(event)
423 | return
424 | }
425 |
426 | //realWorld.futureProjection = undefined
427 | //saveFutureProjection(realWorld)
428 | const clone_world = cloneWorld(realWorld)
429 | //clone_world.futureProjection = undefined
430 | clone_world.future_projection_skip = 30
431 | //clone_world.projection_skip = 1
432 | //clone_world.isProjection = false
433 |
434 | addWorld(multiverse, clone_world)
435 |
436 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
437 | const clone_target = clone_portal.target
438 | const variant = PORTAL_MOVE.enter(event, {target: clone_target})
439 | const clone_froggy = clone_world.atoms.find(a => a.id === event.froggy.id)
440 | print("nowline from", clone_portal, "to", clone_target)
441 |
442 | replaceWorld(realWorld, clone_world)
443 | realWorld.pruneTimer = 30
444 |
445 | variant.fadeReliantOn = clone_froggy
446 | clone_froggy.fadeRelier = variant
447 | variant.isFreezeFadeType = true
448 | //variant.fadeReliantOn = clone_froggy
449 | //clone_froggy.fadeReliantOn = variant
450 | clone_world.fadeReliance = 30
451 |
452 | return
453 |
454 | }
455 | }
456 |
457 | const PORTAL_MAD = {
458 | enter: (event) => {
459 |
460 | //print(event.world.id)
461 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
462 | //print("proj")
463 | }
464 | else {
465 | //print("bye")
466 | PORTAL_VOID.enter(event)
467 | //event.froggy.fadeReliantOn = undefined
468 | if (event.froggy.fadeRelier !== undefined) event.froggy.fadeRelier.d.fadeReliantOn = undefined
469 | event.world.fadeReliance = undefined
470 | return
471 | }
472 |
473 | const realWorld = event.world.realWorld
474 | if (realWorld.isProjection) {
475 | PORTAL_VOID.enter(event)
476 | return
477 | }
478 |
479 | //realWorld.futureProjection = undefined
480 | //saveFutureProjection(realWorld)
481 | const clone_world = cloneWorld(realWorld)
482 | //clone_world.futureProjection = undefined
483 | clone_world.future_projection_skip = 30
484 | //clone_world.projection_skip = 1
485 | //clone_world.isProjection = false
486 |
487 | addWorld(multiverse, clone_world)
488 |
489 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
490 | const clone_target = clone_portal.target
491 | const variant = PORTAL_MOVE.enter(event, {target: clone_target})
492 | const clone_froggy = clone_world.atoms.find(a => a.id === event.froggy.id)
493 | print("nowline from", clone_portal, "to", clone_target)
494 |
495 | replaceWorld(realWorld, clone_world)
496 | realWorld.pruneTimer = 30
497 |
498 | variant.fadeReliantOn = clone_froggy
499 | clone_froggy.fadeRelier = variant
500 | variant.isMadFadeType = true
501 | //variant.fadeReliantOn = clone_froggy
502 | //clone_froggy.fadeReliantOn = variant
503 | clone_world.fadeReliance = 30
504 |
505 | return
506 |
507 | }
508 | }
509 |
510 | const PORTAL_PASTNOW = {
511 | enter: (event) => {
512 |
513 | //print(event.world.id)
514 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
515 | //print("proj")
516 | }
517 | else {
518 | //print("bye")
519 | PORTAL_VOID.enter(event)
520 | return
521 | }
522 |
523 | const realWorld = event.world.realWorld
524 | if (realWorld.isProjection) {
525 | PORTAL_VOID.enter(event)
526 | return
527 | }
528 |
529 | //realWorld.futureProjection = undefined
530 | //saveFutureProjection(realWorld)
531 | const clone_world = cloneWorld(realWorld)
532 | //clone_world.futureProjection = undefined
533 | clone_world.future_projection_skip = 30
534 | //clone_world.projection_skip = 1
535 | //clone_world.isProjection = false
536 |
537 | addWorld(multiverse, clone_world)
538 |
539 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
540 | const clone_target = clone_portal.target
541 | PORTAL_MOVE.enter(event, {target: clone_target})
542 | print("nowline from", clone_portal, "to", clone_target)
543 |
544 | replaceWorld(realWorld, clone_world)
545 | realWorld.pruneTimer = 30
546 |
547 | return
548 |
549 | }
550 | }
551 |
552 | const PORTAL_PASTNOWLINE = {
553 | enter: (event) => {
554 |
555 | //print(event.world.id)
556 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
557 | //print("proj")
558 | }
559 | else {
560 | //print("bye")
561 | PORTAL_VOID.enter(event)
562 | return
563 | }
564 |
565 | const realWorld = event.world.realWorld
566 | if (realWorld.isProjection) {
567 | "hi".d
568 | PORTAL_VOID.enter(event)
569 | return
570 | }
571 |
572 | //realWorld.futureProjection = undefined
573 | //saveFutureProjection(realWorld)
574 | const clone_world = cloneWorld(realWorld)
575 | //clone_world.futureProjection = undefined
576 | clone_world.future_projection_skip = 30
577 | //clone_world.projection_skip = 1
578 | //clone_world.isProjection = false
579 |
580 | addWorld(multiverse, clone_world)
581 |
582 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
583 | const clone_target = clone_portal.target
584 | PORTAL_MOVE.enter(event, {target: clone_target})
585 | print("nowline from", clone_portal, "to", clone_target)
586 |
587 | return
588 |
589 | }
590 | }
591 |
592 | const PORTAL_FUTURENOW = {
593 | enter: (event) => {
594 |
595 | if (event.world.isProjection) {
596 | PORTAL_VOID.enter(event)
597 | return
598 | }
599 |
600 | const projection = event.world.futureProjection
601 |
602 | if (projection === undefined) {
603 | PORTAL_VOID.enter(event)
604 | return
605 | }
606 |
607 | const clone_world = projection
608 | projection.isProjection = false
609 | projection.futureProjection = undefined
610 | event.world.futureProjection = undefined
611 | saveFutureProjection(event.world)
612 | //savePastProjection(clone_world)
613 | /*clone_world.projection_skip = 1*/
614 |
615 | const clone_portal = clone_world.atoms.find(a => a.atom_id === event.portal.atom_id)
616 | const clone_target = clone_portal.target
617 |
618 | print(event.world.isProjection, event.world.id)
619 | addWorld(multiverse, clone_world)
620 | clone_world.futureNowRecordings = []
621 | clone_world.futureNowBaseWorld = event.world
622 |
623 | PORTAL_MOVE.enter(event, {target: clone_target})
624 |
625 | const clone_froggy = clone_world.atoms.find(a => a.atom_id === event.froggy.atom_id).d
626 |
627 | saveFutureProjection(projection)
628 | //event.world.futureProjection = undefined
629 | //saveFutureProjection(event.world)
630 | //if (clone_froggy != undefined) clone_froggy.variantParent = event.froggy
631 | //else print(clone_froggy)
632 |
633 | //clone_froggy.variantParent = event.froggy
634 |
635 | //if (clone_froggy === undefined) return true
636 | //else {
637 | //clone_froggy.variantParent = event.froggy
638 | //}
639 |
640 | //clone_froggy.hh
641 | //replaceWorld(clone_world, event.world)
642 |
643 | return
644 | }
645 | }
646 |
647 | const PORTAL_FUTURELINE = {
648 | enter: (event) => {
649 |
650 | if (event.world.isProjection) {
651 | PORTAL_VOID.enter(event)
652 | return
653 | }
654 |
655 | const projection = event.world.futureProjection
656 |
657 | if (projection === undefined) {
658 | PORTAL_VOID.enter(event)
659 | return
660 | }
661 |
662 | const clone_world = projection
663 | projection.isProjection = false
664 | projection.futureProjection = undefined
665 | event.world.futureProjection = undefined
666 | saveFutureProjection(event.world)
667 | //savePastProjection(clone_world)
668 | /*clone_world.projection_skip = 1*/
669 |
670 | const clone_portal = clone_world.atoms.find(a => a.atom_id === event.portal.atom_id)
671 | const clone_target = clone_portal.target
672 |
673 | print(event.world.isProjection, event.world.id)
674 | addWorld(multiverse, clone_world)
675 |
676 | PORTAL_MOVE.enter(event, {target: clone_target})
677 |
678 | const clone_froggy = clone_world.atoms.find(a => a.atom_id === event.froggy.atom_id).d
679 |
680 | saveFutureProjection(projection)
681 | //event.world.futureProjection = undefined
682 | //saveFutureProjection(event.world)
683 | //if (clone_froggy != undefined) clone_froggy.variantParent = event.froggy
684 | //else print(clone_froggy)
685 |
686 | //clone_froggy.variantParent = event.froggy
687 |
688 | //if (clone_froggy === undefined) return true
689 | //else {
690 | //clone_froggy.variantParent = event.froggy
691 | //}
692 |
693 | //clone_froggy.hh
694 |
695 | return
696 | }
697 | }
698 | const PORTAL_NEXUS = {
699 | enter: (event) => {
700 |
701 | const realWorld = event.world.realWorld
702 | if (event.world.bounceTimer !== undefined) {
703 | //event.froggy.nextdy *= -0.9
704 | //event.world.bounceTimer = undefined
705 | //return
706 | print("nexus")
707 |
708 | return PORTAL_PASTLINE.enter(event)
709 | }
710 |
711 | if (event.world.isCrashTest) {
712 | if (event.froggy.id === event.world.crashNeededFroggy) {
713 | event.world.crashSuccess = true
714 | return "CRASH"
715 | }
716 | }
717 |
718 | //print(event.world.id)
719 | if (event.world.isProjection && event.world.isOnCatchup !== true) {
720 | //print("proj")
721 | }
722 | else {
723 | //print("bye")
724 | PORTAL_VOID.enter(event)
725 | return
726 | }
727 |
728 | if (realWorld.isProjection) {
729 | PORTAL_VOID.enter(event)
730 | return
731 | }
732 |
733 | const crashTestRealWorld = cloneWorld(event.world)
734 | const crashTestWorld = cloneWorld(realWorld)
735 | crashTestWorld.realWorld = crashTestRealWorld
736 | crashTestWorld.future_projection_skip = 30
737 |
738 | crashTestRealWorld.isCrashTest = true
739 | crashTestWorld.isCrashTest = true
740 |
741 | const crash_froggy = crashTestRealWorld.atoms.find(a => a.id === event.froggy.id)
742 | const crash_portal = crashTestWorld.atoms.find(a => a.id === event.portal.id)
743 | const crash_target = crash_portal.target
744 |
745 | crashTestRealWorld.crashNeededFroggy = event.froggy.id
746 | crashTestWorld.crashNeededFroggy = event.froggy.id
747 |
748 | PORTAL_MOVE.enter({portal:crash_portal, froggy: crash_froggy, axis: event.axis}, {target: crash_target})
749 | for (let i = 0; i < 31; i++) {
750 | fullUpdateWorld(crashTestRealWorld)
751 | fullUpdateWorld(crashTestWorld)
752 | }
753 |
754 | if (!crashTestWorld.crashSuccess) {
755 | print("PARADOX")
756 | realWorld.bounceTimer = 30
757 | //realWorld.future_projection_skip = 30
758 | //saveFutureProjection(realWorld)
759 | return PORTAL_VOID.enter(event)
760 | }
761 |
762 |
763 | //realWorld.futureProjection = undefined
764 | //saveFutureProjection(realWorld)
765 | const clone_world = cloneWorld(realWorld)
766 | clone_world.future_projection_skip = 30
767 |
768 | addWorld(multiverse, clone_world)
769 |
770 | const clone_portal = clone_world.atoms.find(a => a.id === event.portal.id)
771 | const clone_target = clone_portal.target
772 | PORTAL_MOVE.enter(event, {target: clone_target})
773 | print("nowline from", clone_portal, "to", clone_target)
774 |
775 | realWorld.isHidden = true
776 | replaceWorld(realWorld, clone_world)
777 | realWorld.pruneTimer = 31
778 |
779 | return
780 |
781 | },
782 | exit: (event) => {
783 | const froggy = event.froggy
784 | froggy.cutTop = 0
785 | froggy.cutLeft = 0
786 | froggy.cutRight = 0
787 | froggy.cutBottom = 0
788 | }
789 | }
790 |
791 | const PORTAL_PASTLINE = {
792 | enter: (event) => {
793 |
794 | // UNCOMMENT WHEN NOT DOING NEXUS
795 | if (event.world.bounceTimer !== undefined) {
796 | return PORTAL_VOID.enter(event)
797 | }
798 |
799 | const projection = event.world.pastProjections[30]
800 |
801 |
802 | if (projection === undefined) {
803 | PORTAL_VOID.enter(event)
804 | return
805 | }
806 |
807 | //print(projection.id)
808 |
809 | const clone_world = cloneWorld(projection)
810 | savePastProjection(clone_world)
811 | clone_world.projection_skip = 1
812 |
813 | const clone_portal = clone_world.atoms[event.portal.atom_id]
814 | const clone_target = clone_portal.target
815 | const clone_froggy = clone_world.atoms[event.froggy.atom_id]
816 |
817 | if (!event.world.isProjection) {
818 | addWorld(multiverse, clone_world)
819 | }
820 | PORTAL_MOVE.enter(event, {target: clone_target})
821 |
822 | clone_froggy.variantParent = event.froggy
823 | clone_world.bounceTimer = 31
824 |
825 | //clone_froggy.d
826 | //event.froggy.links[0].atom.d
827 |
828 | //removeAtom(event.world, variant, {includingChildren: false, destroy: false})
829 | //addAtom(clone_world, variant, {ignoreLinks: false})
830 |
831 | //variant.portals[event.axis.back] = clone_target
832 |
833 | /*clone_variant.parent = froggy
834 | for (const link of froggy.links) {
835 | if (link.atom === variant) {
836 | link.atom = clone_variant
837 | }
838 | }*/
839 |
840 | //removeAtom(event.world, variant, {includingChildren: false})
841 | //removeAtom(clone_world, clone_froggy)
842 |
843 |
844 |
845 | //moveAtomWorld(clone_variant, event.world, clone_world)
846 |
847 |
848 |
849 | return
850 | }
851 | }
852 |
853 | const PORTAL_REFROG = {
854 | enter: (event) => {
855 | const variant = PORTAL_MOVE.enter(event)
856 | if (variant !== undefined) {
857 | variant.refrogTrackPlay = true
858 | }
859 | }
860 | }
861 |
862 | const PORTAL_INVERT = {
863 | enter: (event) => {
864 |
865 | /*if (event.world.bounceTimer !== undefined) {
866 | return PORTAL_VOID.enter(event)
867 | }*/
868 |
869 | const projection = event.world
870 |
871 |
872 | if (projection === undefined) {
873 | PORTAL_VOID.enter(event)
874 | return
875 | }
876 |
877 | //print(projection.id)
878 |
879 | const clone_world = cloneWorld(projection)
880 | savePastProjection(clone_world)
881 | //clone_world.projection_skip = 1
882 |
883 | const clone_portal = clone_world.atoms[event.portal.atom_id]
884 | const clone_target = clone_portal.target
885 | const clone_froggy = clone_world.atoms[event.froggy.atom_id]
886 |
887 | if (!event.world.isProjection) {
888 | addWorld(multiverse, clone_world)
889 | }
890 |
891 | clone_world.rewindAutoPlay = event.world.pastProjections.map(w => cloneWorld(w))
892 | //replaceWorld(clone_world, event.world)
893 | //event.world.isHidden = true
894 | //event.world.pruneTimer = 200
895 | //PORTAL_MOVE.enter(event, {target: clone_target})
896 |
897 | //clone_froggy.variantParent = event.froggy
898 | //clone_world.bounceTimer = 31
899 |
900 | //clone_froggy.d
901 | //event.froggy.links[0].atom.d
902 |
903 | //removeAtom(event.world, variant, {includingChildren: false, destroy: false})
904 | //addAtom(clone_world, variant, {ignoreLinks: false})
905 |
906 | //variant.portals[event.axis.back] = clone_target
907 |
908 | /*clone_variant.parent = froggy
909 | for (const link of froggy.links) {
910 | if (link.atom === variant) {
911 | link.atom = clone_variant
912 | }
913 | }*/
914 |
915 | //removeAtom(event.world, variant, {includingChildren: false})
916 | //removeAtom(clone_world, clone_froggy)
917 |
918 |
919 |
920 | //moveAtomWorld(clone_variant, event.world, clone_world)
921 |
922 | const variant = PORTAL_MOVE.enter(event)
923 | moveAtomWorld(variant, event.world, clone_world)
924 | if (clone_world.bonusAtoms === undefined) {
925 | clone_world.bonusAtoms = []
926 | }
927 | clone_world.bonusAtoms.push(variant)
928 |
929 |
930 | return
931 | }
932 | }
933 |
934 | const PORTAL_REWIND = {
935 | enter: (event) => {
936 |
937 | /*if (event.world.bounceTimer !== undefined) {
938 | return PORTAL_VOID.enter(event)
939 | }*/
940 |
941 | const projection = event.world
942 |
943 |
944 | if (projection === undefined) {
945 | PORTAL_VOID.enter(event)
946 | return
947 | }
948 |
949 | //print(projection.id)
950 |
951 | const clone_world = cloneWorld(projection)
952 | savePastProjection(clone_world)
953 | //clone_world.projection_skip = 1
954 |
955 | const clone_portal = clone_world.atoms[event.portal.atom_id]
956 | const clone_target = clone_portal.target
957 | const clone_froggy = clone_world.atoms[event.froggy.atom_id]
958 |
959 | if (!event.world.isProjection) {
960 | addWorld(multiverse, clone_world)
961 | }
962 |
963 | clone_world.rewindAutoPlay = event.world.pastProjections.map(w => cloneWorld(w))
964 |
965 | //PORTAL_MOVE.enter(event, {target: clone_target})
966 |
967 | //clone_froggy.variantParent = event.froggy
968 | //clone_world.bounceTimer = 31
969 |
970 | //clone_froggy.d
971 | //event.froggy.links[0].atom.d
972 |
973 | //removeAtom(event.world, variant, {includingChildren: false, destroy: false})
974 | //addAtom(clone_world, variant, {ignoreLinks: false})
975 |
976 | //variant.portals[event.axis.back] = clone_target
977 |
978 | /*clone_variant.parent = froggy
979 | for (const link of froggy.links) {
980 | if (link.atom === variant) {
981 | link.atom = clone_variant
982 | }
983 | }*/
984 |
985 | //removeAtom(event.world, variant, {includingChildren: false})
986 | //removeAtom(clone_world, clone_froggy)
987 |
988 |
989 |
990 | //moveAtomWorld(clone_variant, event.world, clone_world)
991 |
992 |
993 |
994 | return
995 | }
996 | }
997 |
998 | const PORTAL_REWRITE = {
999 | enter: (event) => {
1000 |
1001 | if (event.world.bounceTimer !== undefined) {
1002 | return PORTAL_VOID.enter(event)
1003 | }
1004 |
1005 | const projection = event.world.pastProjections[30]
1006 |
1007 | if (projection === undefined) {
1008 | PORTAL_VOID.enter(event)
1009 | return
1010 | }
1011 |
1012 | //print(projection.id)
1013 |
1014 | const clone_world = cloneWorld(projection)
1015 | savePastProjection(clone_world)
1016 | clone_world.projection_skip = 1
1017 |
1018 | const clone_portal = clone_world.atoms[event.portal.atom_id]
1019 | const clone_target = clone_portal.target
1020 | const clone_froggy = clone_world.atoms[event.froggy.atom_id]
1021 |
1022 | if (!event.world.isProjection) {
1023 | addWorld(multiverse, clone_world)
1024 | }
1025 | PORTAL_MOVE.enter(event, {target: clone_target})
1026 |
1027 | clone_froggy.variantParent = event.froggy
1028 |
1029 | replaceWorld(event.world, clone_world)
1030 |
1031 | event.world.pruneTimer = 30
1032 | event.world.isHidden = true
1033 | clone_world.bounceTimer = 31
1034 |
1035 | //clone_froggy.d
1036 | //event.froggy.links[0].atom.d
1037 |
1038 | //removeAtom(event.world, variant, {includingChildren: false, destroy: false})
1039 | //addAtom(clone_world, variant, {ignoreLinks: false})
1040 |
1041 | //variant.portals[event.axis.back] = clone_target
1042 |
1043 | /*clone_variant.parent = froggy
1044 | for (const link of froggy.links) {
1045 | if (link.atom === variant) {
1046 | link.atom = clone_variant
1047 | }
1048 | }*/
1049 |
1050 | //removeAtom(event.world, variant, {includingChildren: false})
1051 | //removeAtom(clone_world, clone_froggy)
1052 |
1053 |
1054 |
1055 | //moveAtomWorld(clone_variant, event.world, clone_world)
1056 |
1057 |
1058 |
1059 | return
1060 | }
1061 | }
1062 |
1063 | const PORTAL_DIMENSION = {
1064 | enter: (event) => {
1065 |
1066 |
1067 |
1068 | if (event.world.isProjection) {
1069 | //PORTAL_VOID.enter(event)
1070 | return
1071 | }
1072 |
1073 |
1074 | const clone_world = cloneWorld(event.world)
1075 | addWorld(multiverse, clone_world)
1076 |
1077 | const clone_portal = clone_world.atoms[event.portal.atom_id]
1078 | const clone_target = clone_portal.target
1079 |
1080 | const variant = PORTAL_MOVE.enter(event, {target: clone_target})
1081 | const clone_froggy = clone_world.atoms[event.froggy.atom_id]
1082 | const clone_variant = clone_world.atoms[variant.atom_id]
1083 | const froggy = event.froggy
1084 |
1085 | /*clone_variant.parent = froggy
1086 | for (const link of froggy.links) {
1087 | if (link.atom === variant) {
1088 | link.atom = clone_variant
1089 | }
1090 | }*/
1091 |
1092 | //removeAtom(event.world, variant, {includingChildren: false})
1093 | removeAtom(clone_world, clone_froggy, {includingChildren: false})
1094 |
1095 |
1096 |
1097 | return
1098 | }
1099 | }
1100 |
1101 | const PORTAL_MOVE = {
1102 | enter: ({portal, pbounds, froggy, world, axis, blockers}, {target = portal.target} = {}) => {
1103 | if (target !== undefined) {
1104 |
1105 | // UNCOMMENT FOR SOME THINGS!?!?!
1106 | /*if (world !== undefined && world.isProjection) {
1107 | return
1108 | }*/
1109 |
1110 | const variant = cloneAtom(froggy)
1111 | variant.fling = target.turns - portal.turns
1112 | while (variant.fling < 0) {
1113 | variant.fling += 4
1114 | }
1115 | //if (variant.fling === 3) variant.fling = 1
1116 |
1117 | const size = (variant.turns % 2 === 0)? variant[axis.sizeName] : variant[axis.otherSizeName]
1118 | variant[axis.cutBackName] = size
1119 | variant[axis.cutFrontName] = 0
1120 |
1121 | variant.links = []
1122 | if (froggy !== undefined && froggy.world !== undefined) {
1123 | variant.update = froggy.world.atoms.includes(froggy)? UPDATE_NONE : froggy.update
1124 | }
1125 |
1126 | //variant.portals.d
1127 | //froggy.portals.d
1128 |
1129 | let displacementOther = 0
1130 | let displacement = 0
1131 |
1132 | if (variant.fling === 0) {
1133 |
1134 | variant.portals[axis.front] = undefined
1135 | variant.portals[axis.back] = target
1136 | displacement = target[axis.name] - portal[axis.name]
1137 | displacement += target[axis.sizeName] * axis.direction // Go to other side of portal
1138 |
1139 | displacementOther = target[axis.other.name] - portal[axis.other.name]
1140 |
1141 |
1142 | variant.onPromote = (self) => {
1143 | self.update = froggy.update
1144 | self.skipUpdate = true
1145 | self.fling = undefined
1146 | }
1147 |
1148 | const link = linkAtom(froggy, variant, {
1149 | [axis.other.name]: v => v + displacementOther,
1150 | [axis.name]: v => v + displacement,
1151 | ["turns"]: (them, me) => me,
1152 | ["width"]: (them, me) => me,
1153 |
1154 |
1155 | ["nextdx"]: () => froggy.dx,
1156 | ["nextdy"]: () => froggy.dy,
1157 |
1158 | ["dx"]: () => froggy.dx,
1159 | ["dy"]: () => froggy.dy,
1160 |
1161 | ["height"]: (them, me) => me,
1162 | ["flipX"]: (them, me) => me,
1163 | })
1164 | }
1165 | else if (variant.fling === 1) {
1166 |
1167 | variant.portals[axis.flingFrontName] = undefined
1168 | variant.portals[axis.flingBackName] = target
1169 |
1170 | const variantStartingPlaceOther = target[axis.other.name]
1171 | const froggyStartingPlaceOther = portal[axis.name] - froggy[axis.sizeName]
1172 |
1173 | const variantStartingPlace = target[axis.name] + (froggy[axis.other.name] - portal[axis.other.name])
1174 | const froggyStartingPlace = froggy[axis.other.name]
1175 |
1176 | variant.onPromote = (self) => {
1177 | self.update = froggy.update
1178 | self.skipUpdate = true
1179 | self.fling = undefined
1180 | }
1181 |
1182 | const flipXDirection = froggy.flipX? -1 : 1
1183 | const link = linkAtom(froggy, variant, {
1184 | [axis.other.name]: () => variantStartingPlaceOther - (froggy[axis.name] - froggyStartingPlaceOther) - 1, //TODO: remove need for minus one
1185 | [axis.name]: () => variantStartingPlace + (froggy[axis.other.name] - froggyStartingPlace),
1186 | ["turns"]: (them, me) => me,
1187 | ["width"]: (them, me) => me,
1188 | ["height"]: (them, me) => me,
1189 |
1190 | ["nextdx"]: () => -froggy.dy,
1191 | ["nextdy"]: () => froggy.dx,
1192 |
1193 | ["dx"]: () => -froggy.dy,
1194 | ["dy"]: () => froggy.dx,
1195 | ["flipX"]: (them, me) => me,
1196 | })
1197 | }
1198 | else if (variant.fling === 2) {
1199 | throw new Error(`[TimePond] Unimplemented fling type ${variant.fling}`)
1200 | }
1201 | else if (variant.fling === 3) {
1202 | throw new Error(`[TimePond] Unimplemented fling type ${variant.fling}`)
1203 | }
1204 | else {
1205 | throw new Error(`[TimePond] Invalid fling type ${variant.fling}... Please tell @todepond`)
1206 | }
1207 |
1208 | const flipXDirection = froggy.flipX? -1 : 1
1209 | updateAtomLinks(froggy)
1210 | variant.turns = froggy.turns //band-aid because makeAtom doesn't do turns properly
1211 |
1212 |
1213 | addAtom(target.world, variant)
1214 | turnAtom(variant, variant.fling * flipXDirection, false, false, target.world, [], true)
1215 | //if (variant.turns % 4 === 1) flipAtom(variant)
1216 |
1217 | //variant.prevBounds = getBounds(variant)
1218 |
1219 | return variant
1220 | }
1221 | },
1222 | end: () => {},
1223 | moveIn: () => {},
1224 | moveOut: () => {},
1225 | leave: () => {},
1226 | }
1227 |
1228 | //===========//
1229 | // Colliders //
1230 | //===========//
1231 | // TODO: stop the rotate potion from rotating portals/frogs that are currently portaling because it would tear atoms in half
1232 | const COLLIDE_POTION_ROTATE = ({self, atom, axis, world}) => {
1233 | if (self.used) return
1234 | atom = getAtomAncestor(atom)
1235 | if (!atom.isVoid && !atom.isPotion) {
1236 | world.atoms = world.atoms.filter(a => a !== self)
1237 | if (!atom.isMover) turnAtom(atom, 1, true, true, world, [self])
1238 | else atom.nextturns++
1239 | self.used = true
1240 | //atom.nextdx = 0
1241 | atom.nextdy = -5
1242 | atom.jumpTick = 0
1243 | return false
1244 | }
1245 | }
1246 |
1247 | // TODO: stop the rotate potion from rotating portals/frogs that are currently portaling because it would tear atoms in half
1248 | const COLLIDED_POTION_ROTATE = ({self, atom, world}) => {
1249 | if (atom.used) return
1250 | atom = getAtomAncestor(atom)
1251 | if (!self.isVoid && !self.isPotion) {
1252 | world.atoms = world.atoms.filter(a => a !== atom)
1253 | if (!self.isMover) turnAtom(self, 1, true, true, world, [atom])
1254 | else self.nextturns++
1255 | atom.used = true
1256 | //atom.nextdx = 0
1257 | self.nextdy = -5
1258 | self.jumpTick = 0
1259 | return false
1260 | }
1261 | }
1262 |
1263 | const COLLIDED_PORTAL = ({self, bself, atom, axis, baxis, world, bounds, nbounds, abounds, iveHitSomething}) => {
1264 |
1265 | // Only allow going through this portal in the correct axis
1266 | const portalIsHoriz = atom.turns % 2 === 0
1267 | const movementIsVert = axis.dname === "dy"
1268 | if (portalIsHoriz !== movementIsVert) {
1269 | return true
1270 | }
1271 |
1272 | //==================================================//
1273 | // BUMP edges of portal if I'm NOT going through it //
1274 | //==================================================//
1275 | /*if (bself.portals[axis.front] !== atom) {
1276 |
1277 | const reach = [bounds[axis.other.small], bounds[axis.other.big]]
1278 | const nreach = [nbounds[axis.other.small], nbounds[axis.other.big]]
1279 |
1280 | const sideBumps = {
1281 | small: aligns(reach, nreach, [abounds[axis.other.small]]),
1282 | big: aligns(reach, nreach, [abounds[axis.other.big]]),
1283 | }
1284 |
1285 | // Collide with the edges of the portal
1286 | if (sideBumps.small || sideBumps.big) {
1287 | self.slip = 0.95
1288 | return true
1289 | }
1290 | }*/
1291 |
1292 | // Otherwise, go through...
1293 |
1294 | const portalIsNew = bself.portals[axis.front] === undefined
1295 |
1296 | let induceError = false
1297 |
1298 | // BEGIN to cut myself down to go into portal
1299 | if (portalIsNew) {
1300 |
1301 | if (iveHitSomething) return true
1302 | //const amountInPortal = axis.direction * (nbounds[axis.front] - abounds[axis.back])
1303 | //bself[axis.cutFrontName] += amountInPortal
1304 | //const remainingSize = baxis.size - bself[axis.cutBackName]
1305 | /*if (bself[axis.cutFrontName] >= remainingSize) {
1306 | removeAtom(world, bself, {includingChildren: false, destroy: true})
1307 | }*/
1308 |
1309 | if (bself[axis.cutFrontName] < 0) {
1310 | bself[axis.cutFrontName] = 0
1311 | return false
1312 | }
1313 |
1314 | // Register (or re-register) that I am currently using this portal
1315 | bself.portals[axis.front] = atom
1316 |
1317 | if (atom.portal.enter !== undefined) {
1318 | const result = atom.portal.enter({pbounds: abounds, fnbounds: nbounds, portal: atom, froggy: bself, world, axis})
1319 | if (result === true) induceError = true
1320 | }
1321 |
1322 |
1323 | }
1324 |
1325 | // CONTINUE to cut myself down to go into portal
1326 | else {
1327 |
1328 | const amountInPortal = axis.direction * (nbounds[axis.front] - abounds[axis.back])
1329 | bself[axis.cutFrontName] += amountInPortal
1330 | const remainingSize = baxis.size/* - bself[axis.cutBackName]*/
1331 | if (bself[axis.cutFrontName] >= remainingSize) {
1332 | removeAtom(world, bself, {includingChildren: false, destroy: true})
1333 | }
1334 |
1335 | if (bself[axis.cutFrontName] < 0) {
1336 | bself[axis.cutFrontName] = 0
1337 | return false
1338 | }
1339 |
1340 | }
1341 | //bself.portals.d
1342 |
1343 | if (atom.portal.move !== undefined) atom.portal.move()
1344 | if (atom.portal.moveIn !== undefined) atom.portal.moveIn()
1345 |
1346 | if (bself.portals[axis.front] !== atom) {
1347 | return true
1348 | //throw new Error(`[TimePond] An atom tried to go through two portals in the same direction.`)
1349 | }
1350 |
1351 | if (induceError) return "induce"
1352 | return false
1353 |
1354 | }
1355 |
1356 | //==========//
1357 | // Elements //
1358 | //==========//
1359 | const ELEMENT_BOX = {
1360 | colour: Colour.Orange,
1361 | draw: DRAW_RECTANGLE,
1362 | update: UPDATE_MOVER,
1363 | grab: GRAB_DRAG,
1364 | isMover: true,
1365 | width: 40,
1366 | height: 40,
1367 | /*autoLinks: [
1368 | {
1369 | element: {draw: DRAW_RECTANGLE, grab: GRAB_LINKEE, colour: Colour.Black, update: UPDATE_NONE, isVisual: true},
1370 | offset: {width: () => 2, x: (x) => x},
1371 | },
1372 | {
1373 | element: {draw: DRAW_RECTANGLE, grab: GRAB_LINKEE, colour: Colour.Black, update: UPDATE_NONE, isVisual: true},
1374 | offset: {width: () => 2, x: (x) => x+40-2},
1375 | },
1376 | {
1377 | element: {draw: DRAW_RECTANGLE, grab: GRAB_LINKEE, colour: Colour.Black, update: UPDATE_NONE, isVisual: true},
1378 | offset: {height: () => 2, x: (x) => x},
1379 | },
1380 | ]*/
1381 | }
1382 |
1383 | const ELEMENT_LEAF = {
1384 | colour: Colour.Green,
1385 | draw: DRAW_RECTANGLE,
1386 | update: UPDATE_MOVER,
1387 | grab: GRAB_DRAG,
1388 | isMover: true,
1389 | width: 20,
1390 | height: 10,
1391 | maxSpeed: 2,
1392 | }
1393 |
1394 | const ELEMENT_PLATFORM = {
1395 | colour: Colour.Silver,
1396 | draw: DRAW_RECTANGLE,
1397 | update: UPDATE_STATIC,
1398 | grab: GRAB_DRAG,
1399 | width: 150,
1400 | height: 10,
1401 | }
1402 |
1403 | const ELEMENT_VOID = {
1404 | colour: INVERT? Colour.Black : Colour.Black,
1405 | draw: DRAW_RECTANGLE,
1406 | update: UPDATE_STATIC,
1407 | grab: GRAB_STATIC,
1408 | height: 10,
1409 | width: WORLD_WIDTH,
1410 | isVoid: true,
1411 | y: 0,
1412 | }
1413 |
1414 | const ELEMENT_SPAWNER = {
1415 | update: UPDATE_STATIC,
1416 | draw: DRAW_SPAWNER,
1417 | grab: GRAB_SPAWNER,
1418 | spawn: ELEMENT_BOX,
1419 | }
1420 |
1421 | const ELEMENT_SPAWNER_PORTAL = {
1422 | ...ELEMENT_SPAWNER,
1423 | grab: GRAB_SPAWNER_PORTAL,
1424 | }
1425 |
1426 | const ELEMENT_PORTAL_COLOURS = [
1427 | Colour.Purple,
1428 | Colour.Orange,
1429 | Colour.Green,
1430 | Colour.Pink,
1431 | Colour.Yellow,
1432 | Colour.Cyan,
1433 | Colour.Red,
1434 | ]
1435 | const ELEMENT_PORTAL = {
1436 | update: UPDATE_STATIC,
1437 | draw: DRAW_RECTANGLE,
1438 | grab: GRAB_DRAG,
1439 | height: 5,
1440 | width: 125,
1441 | colour: Colour.Purple,
1442 | isPortal: true,
1443 | isPortalActive: true,
1444 | preCollided: COLLIDED_PORTAL,
1445 | autoLinks: [
1446 | /*{
1447 | element: {...ELEMENT_PLATFORM, grab: GRAB_LINKEE, colour: Colour.Black},
1448 | offset: {width: (w) => w, y: (y) => y-2, x: (x) => x, height: () => 2,},
1449 | },*/
1450 | {
1451 | element: {...ELEMENT_PLATFORM, grab: GRAB_LINKEE, colour: INVERT? Colour.White : Colour.Black, foo: "hi"},
1452 | offset: {width: () => 2, x: (x) => x-2},
1453 | },
1454 | {
1455 | element: {...ELEMENT_PLATFORM, grab: GRAB_LINKEE, colour: INVERT? Colour.White : Colour.Black, foo: "hi"},
1456 | offset: {width: () => 2, x: (x) => x+125},
1457 | },
1458 | /*{
1459 | element: {...ELEMENT_PLATFORM, grab: GRAB_LINKEE, colour: Colour.Black},
1460 | offset: {width: (w) => w+4, y: (y) => y+5, x: (x) => x-2, height: () => 2,},
1461 | },*/
1462 | ]
1463 |
1464 | }
1465 |
1466 | const ELEMENT_LILYPAD = {
1467 | update: UPDATE_STATIC,
1468 | draw: DRAW_RECTANGLE,
1469 | grab: GRAB_DRAG,
1470 | bounce: 15,
1471 | height: 8,
1472 | width: 80,
1473 | colour: Colour.Green,
1474 | }
1475 |
1476 | const ELEMENT_PORTAL_VOID = {
1477 | ...ELEMENT_PORTAL,
1478 | portal: PORTAL_VOID,
1479 | colour: Colour.White,
1480 | }
1481 |
1482 | const makePortalTargeter = () => {
1483 | let lonelyPortal = undefined
1484 | return (portal) => {
1485 | if (portal.isMenuItem) return
1486 | if (lonelyPortal === undefined) {
1487 | lonelyPortal = portal
1488 | return
1489 | }
1490 |
1491 | lonelyPortal.target = portal
1492 | portal.target = lonelyPortal
1493 | lonelyPortal = undefined
1494 | }
1495 | }
1496 | const ELEMENT_PORTAL_MOVE = {
1497 | ...ELEMENT_PORTAL,
1498 | portal: PORTAL_MOVE,
1499 | colour: Colour.Orange,
1500 | construct: makePortalTargeter(),
1501 | }
1502 |
1503 | const ELEMENT_PORTAL_DIMENSION = {
1504 | ...ELEMENT_PORTAL,
1505 | portal: PORTAL_DIMENSION,
1506 | colour: Colour.Blue,
1507 | construct: makePortalTargeter(),
1508 | }
1509 |
1510 | const ELEMENT_PORTAL_REWRITE = {
1511 | ...ELEMENT_PORTAL,
1512 | portal: PORTAL_REWRITE,
1513 | colour: Colour.Yellow,
1514 | construct: makePortalTargeter(),
1515 | }
1516 |
1517 | const ELEMENT_PORTAL_PASTLINE = {
1518 | ...ELEMENT_PORTAL,
1519 | portal: PORTAL_PASTLINE,
1520 | colour: Colour.Yellow,
1521 | construct: makePortalTargeter(),
1522 | }
1523 |
1524 | const ELEMENT_PORTAL_REWIND = {
1525 | ...ELEMENT_PORTAL,
1526 | portal: PORTAL_REWIND,
1527 | colour: Colour.Yellow,
1528 | construct: makePortalTargeter(),
1529 | }
1530 |
1531 |
1532 | const ELEMENT_PORTAL_NEXUS = {
1533 | ...ELEMENT_PORTAL,
1534 | portal: PORTAL_NEXUS,
1535 | colour: Colour.Yellow,
1536 | construct: makePortalTargeter(),
1537 | requiresFutureProjections: true,
1538 | }
1539 |
1540 | const ELEMENT_PORTAL_FUTURELINE = {
1541 | ...ELEMENT_PORTAL,
1542 | portal: PORTAL_FUTURELINE,
1543 | colour: Colour.Red,
1544 | construct: makePortalTargeter(),
1545 | requiresFutureProjections: true,
1546 | }
1547 |
1548 | const ELEMENT_PORTAL_PASTNOWLINE = {
1549 | ...ELEMENT_PORTAL,
1550 | portal: PORTAL_PASTNOWLINE,
1551 | colour: Colour.Purple,
1552 | construct: makePortalTargeter(),
1553 | requiresFutureProjections: true,
1554 | }
1555 |
1556 | const ELEMENT_PORTAL_PASTNOW = {
1557 | ...ELEMENT_PORTAL,
1558 | portal: PORTAL_PASTNOW,
1559 | colour: Colour.Cyan,
1560 | construct: makePortalTargeter(),
1561 | requiresFutureProjections: true,
1562 | }
1563 |
1564 | const ELEMENT_PORTAL_FUTURENOW = {
1565 | ...ELEMENT_PORTAL,
1566 | portal: PORTAL_FUTURENOW,
1567 | colour: Colour.Red,
1568 | construct: makePortalTargeter(),
1569 | requiresFutureProjections: true,
1570 | }
1571 |
1572 | const ELEMENT_PORTAL_BOUNCE = {
1573 | ...ELEMENT_PORTAL,
1574 | portal: PORTAL_BOUNCE,
1575 | colour: Colour.Cyan,
1576 | construct: makePortalTargeter(),
1577 | requiresFutureProjections: true,
1578 | }
1579 |
1580 | const ELEMENT_PORTAL_INVERT = {
1581 | ...ELEMENT_PORTAL,
1582 | portal: PORTAL_INVERT,
1583 | colour: Colour.Black,
1584 | construct: makePortalTargeter(),
1585 | requiresFutureProjections: true,
1586 | futureProjLength: 30,
1587 | requiresRefrogTracking: true,
1588 | }
1589 |
1590 |
1591 | const ELEMENT_PORTAL_FADE = {
1592 | ...ELEMENT_PORTAL,
1593 | portal: PORTAL_FADE,
1594 | colour: Colour.Cyan,
1595 | construct: makePortalTargeter(),
1596 | requiresFutureProjections: true,
1597 | }
1598 |
1599 |
1600 | const ELEMENT_PORTAL_FREEZE = {
1601 | ...ELEMENT_PORTAL,
1602 | portal: PORTAL_FREEZE,
1603 | colour: Colour.Cyan,
1604 | construct: makePortalTargeter(),
1605 | requiresFutureProjections: true,
1606 | }
1607 |
1608 | const ELEMENT_PORTAL_MAD = {
1609 | ...ELEMENT_PORTAL,
1610 | portal: PORTAL_MAD,
1611 | colour: Colour.Cyan,
1612 | construct: makePortalTargeter(),
1613 | requiresFutureProjections: true,
1614 | }
1615 |
1616 | const ELEMENT_PORTAL_REFROG = {
1617 | ...ELEMENT_PORTAL,
1618 | portal: PORTAL_REFROG,
1619 | colour: Colour.Black,
1620 | construct: makePortalTargeter(),
1621 | //requiresFutureProjections: true,
1622 | requiresRefrogTracking: true,
1623 | }
1624 |
1625 | const ELEMENT_POTION = {
1626 | colour: Colour.Purple,
1627 | draw: DRAW_CIRCLE,
1628 | update: UPDATE_MOVER,
1629 | height: 20,
1630 | width: 20,
1631 | isPotion: true,
1632 | //preCollide: COLLIDE_POTION_ROTATE,
1633 | }
1634 |
1635 | const ELEMENT_POTION_ROTATE = {
1636 | ...ELEMENT_POTION,
1637 | colour: Colour.Orange,
1638 | preCollide: COLLIDE_POTION_ROTATE,
1639 | preCollided: COLLIDED_POTION_ROTATE,
1640 | }
1641 |
1642 | const ELEMENT_FROG = {
1643 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1644 | draw: DRAW_IMAGE,
1645 | update: UPDATE_MOVER_BEING,
1646 | grab: GRAB_DRAG,
1647 | source: "images/Blank@0.25x.png",
1648 | width: 354/6/* - 11 - 7*/,
1649 | height: 254/6,
1650 | isMover: true,
1651 | //cutTop: 10,
1652 | //cutBottom: 10,
1653 | //cutRight: 20,
1654 | //cutLeft: 20,
1655 | showBounds: FROGGY_BOUNDS,
1656 | }
1657 |
1658 | const ELEMENT_FROG_YELLOW = {
1659 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1660 | draw: DRAW_IMAGE,
1661 | update: UPDATE_MOVER_BEING,
1662 | grab: GRAB_DRAG,
1663 | source: "images/Yellow/Other@0.25x.png",
1664 | width: 354/6/* - 11 - 7*/,
1665 | height: 254/6,
1666 | isMover: true,
1667 | //cutTop: 10,
1668 | //cutBottom: 10,
1669 | //cutRight: 20,
1670 | //cutLeft: 20,
1671 | showBounds: FROGGY_BOUNDS,
1672 | }
1673 |
1674 | const ELEMENT_FROG_CYAN = {
1675 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1676 | draw: DRAW_IMAGE,
1677 | update: UPDATE_MOVER_BEING,
1678 | grab: GRAB_DRAG,
1679 | source: "images/Cyan/Other@0.25x.png",
1680 | width: 354/6/* - 11 - 7*/,
1681 | height: 254/6,
1682 | isMover: true,
1683 | //cutTop: 10,
1684 | //cutBottom: 10,
1685 | //cutRight: 20,
1686 | //cutLeft: 20,
1687 | showBounds: FROGGY_BOUNDS,
1688 | }
1689 |
1690 | const ELEMENT_FROG_CYAN_MAD = {
1691 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1692 | draw: DRAW_IMAGE,
1693 | update: UPDATE_MOVER_BEING,
1694 | grab: GRAB_DRAG,
1695 | source: "images/Cyan/Mad@0.25x.png",
1696 | width: 100 * 0.75/* - 11 - 7*/,
1697 | height: 84 * 0.75,
1698 | isMover: true,
1699 | //cutTop: 10,
1700 | //cutBottom: 10,
1701 | //cutRight: 20,
1702 | //cutLeft: 20,
1703 | showBounds: FROGGY_BOUNDS,
1704 | }
1705 |
1706 | const ELEMENT_FROG_PURPLE = {
1707 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1708 | draw: DRAW_IMAGE,
1709 | update: UPDATE_MOVER_BEING,
1710 | grab: GRAB_DRAG,
1711 | source: "images/Purple/Other@0.25x.png",
1712 | width: 354/6/* - 11 - 7*/,
1713 | height: 254/6,
1714 | isMover: true,
1715 | //cutTop: 10,
1716 | //cutBottom: 10,
1717 | //cutRight: 20,
1718 | //cutLeft: 20,
1719 | showBounds: FROGGY_BOUNDS,
1720 | }
1721 |
1722 | const ELEMENT_FROG_GREEN = {
1723 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1724 | draw: DRAW_IMAGE,
1725 | update: UPDATE_MOVER_BEING,
1726 | grab: GRAB_DRAG,
1727 | source: "images/Green/Other@0.25x.png",
1728 | width: 354/6/* - 11 - 7*/,
1729 | height: 254/6,
1730 | isMover: true,
1731 | //cutTop: 10,
1732 | //cutBottom: 10,
1733 | //cutRight: 20,
1734 | //cutLeft: 20,
1735 | showBounds: FROGGY_BOUNDS,
1736 | }
1737 |
1738 | const ELEMENT_FROG_BLACK = {
1739 | //filter: "invert(58%) sepia(77%) saturate(5933%) hue-rotate(336deg) brightness(110%) contrast(108%)",
1740 | draw: DRAW_IMAGE,
1741 | update: UPDATE_MOVER_BEING,
1742 | grab: GRAB_DRAG,
1743 | source: "images/Black/Other@0.25x.png",
1744 | width: 354/6/* - 11 - 7*/,
1745 | height: 254/6,
1746 | isMover: true,
1747 | //cutTop: 10,
1748 | //cutBottom: 10,
1749 | //cutRight: 20,
1750 | //cutLeft: 20,
1751 | showBounds: FROGGY_BOUNDS,
1752 | }
1753 |
1754 | const ELEMENT_BOX_DOUBLE = {
1755 | ...ELEMENT_BOX,
1756 | update: UPDATE_MOVER,
1757 | isMover: false,
1758 | autoLinks: [
1759 | //...ELEMENT_aaBOX.autoLinks,
1760 | {
1761 | element: {...ELEMENT_BOX, update: UPDATE_STATIC, grab: GRAB_LINKEE, onPromote: (self) => {
1762 | self.update = UPDATE_MOVER
1763 | self.grab = GRAB_DRAG
1764 | }},
1765 | offset: {
1766 | x: (v) => v + 50,
1767 | },
1768 | },
1769 | {
1770 | element: {...ELEMENT_BOX, update: UPDATE_STATIC, grab: GRAB_LINKEE, onPromote: (self) => {
1771 | self.update = UPDATE_MOVER
1772 | self.grab = GRAB_DRAG
1773 | }},
1774 | offset: {
1775 | x: (v) => v + 100,
1776 | y: (v) => v + 10,
1777 | },
1778 | },
1779 | ]
1780 | }
--------------------------------------------------------------------------------
/source/main.js:
--------------------------------------------------------------------------------
1 | //=======//
2 | // Setup //
3 | //=======//
4 | const multiverse = makeMultiverse()
5 | const canvas = makeMultiverseCanvas(multiverse)
6 |
7 | on.load(() => {
8 | document.body.style["background-color"] = Colour.Black
9 | document.body.style["overflow-x"] = "hidden"
10 | document.body.style["margin"] = "0"
11 | document.body.appendChild(canvas)
12 | trigger("resize")
13 | })
14 |
15 | on.keydown((e) => {
16 | if (e.key === " ") {
17 | PAUSED = !PAUSED
18 | e.preventDefault()
19 | }
20 | else if (e.key === "ArrowRight") {
21 | STEP = true
22 | PAUSED = true
23 | }
24 | })
25 |
--------------------------------------------------------------------------------
/source/mover.js:
--------------------------------------------------------------------------------
1 |
2 | const moverUpdate = (self, world) => {
3 |
4 | if (self.refrogTrackPlay === true) {
5 | if (!(self.refrogTrack instanceof Array) && self.refrogTrack instanceof Object) {
6 | self.refrogTrack = Array.from(self.refrogTrack)
7 | }
8 | const step = self.refrogTrack.pop()
9 | const prev = self.refrogTrackPrev
10 | if (prev !== undefined && step !== undefined) {
11 | const dx = step.x - prev.x
12 | const dy = step.y - prev.y
13 | self.dx = dx
14 | self.dy = dy
15 | self.nextdx = dx
16 | self.nextdy = dy
17 | }
18 | self.refrogTrackPrev = step
19 |
20 | if (self.refrogTrack.length === 0) {
21 | self.refrogTrackPlay = undefined
22 | self.refrogTrack = undefined
23 | self.refrogTrackPrev = undefined
24 | }
25 |
26 | }
27 |
28 | //self.prevBounds = {...getBounds(self)}
29 | const hit = moverMove(self, world, self.dx, self.dy)
30 | if (hit) {
31 | self.refrogTrackPlay = undefined
32 | self.refrogTrack = undefined
33 | self.refrogTrackPrev = undefined
34 |
35 | if (world.bonusAtoms !== undefined && world.bonusAtoms.includes(self)) {
36 | world.rewindPrev = undefined
37 | world.rewindAutoPlay = undefined
38 | }
39 | }
40 | }
41 |
42 | const moverMove = (self, world, dx, dy) => {
43 |
44 | // Reset some game state info
45 | self.grounded = false
46 | self.slip = undefined
47 |
48 | // Make generalised axes info
49 | // This help me write axis-independent and direction-independent code
50 | const axes = makeAxesInfo(self.x, self.y, dx, dy)
51 |
52 | // Make a list of atoms in this molecule that could POTENTIALLY hit something (ie: not a pure visual)
53 | // With each atom, store info about their potential movement
54 | const candidates = makeCandidates(self, axes)
55 |
56 | // Make each collision candidate emerge from portals if needed
57 | candidates.forEach(candidate => emergeCandidate(candidate, candidate.axes))
58 |
59 | // Find the FIRST atom I would hit if I travel forever in each axis //
60 | const candidateBlockers = candidates.map(candidate => getBlockers(candidate, axes))
61 | const blockersX = candidateBlockers.map(blocker => blocker.dx).flat(1)
62 | const blockersY = candidateBlockers.map(blocker => blocker.dy).flat(1)
63 | const blockers = {dx: blockersX, dy: blockersY}
64 |
65 | // Order the things I would hit from nearest to furthest away
66 | // If there are any draws, put portals last
67 | orderBlockers(blockers, axes)
68 |
69 | let induce = false
70 | //===================================================//
71 | // COLLIDE with the closest atoms to me in each axis //
72 | //===================================================//
73 | let iveReallyHitSomething = false
74 | for (const axis of axes) {
75 |
76 | let iveHitSomething = false
77 | const oldNew = axis.new //haha 'oldNew'
78 |
79 | for (const blocker of blockers[axis.dname]) {
80 | const {atom} = blocker
81 | if (atom === undefined) continue
82 |
83 | const bbounds = blocker.bounds
84 | const baxis = blocker.candidate.axes[axis.dname]
85 | const bself = blocker.candidate.atom
86 |
87 | // Update blocker bounds based on the decided Hit
88 | if (iveHitSomething) {
89 | const correction = oldNew - axis.new
90 | baxis.new -= correction
91 | blocker.candidate.nbounds[axis.front] -= correction
92 | blocker.candidate.nbounds[axis.back] -= correction
93 | }
94 |
95 | // Allow MODs by elements/atoms
96 | // TODO: Here is the problem
97 | // because one of these mod functions could ADD a child to me...
98 | // which I don't check for collisions properly
99 | // Can I check for children after all others? If there any unaccounted for children, also test them maybe?
100 | // Maybe not I dunno
101 | // WHO KNOWS
102 | // But what I do know, is that itll be easier if I create more generalized functions of all the stuff Ive got here TBH
103 | let modResult = true
104 | if (bself.preCollide !== undefined) {
105 | const result = bself.preCollide({self, bself, atom, axis, baxis, world, bounds: blocker.cbounds, nbounds: blocker.cnbounds, abounds: blocker.bounds, iveHitSomething, blockers: blockers[axis.dname]})
106 | if (result === false) modResult = false
107 | if (result === "induce") modResult = result
108 | }
109 | if (atom.preCollided !== undefined) {
110 | const result = atom.preCollided({self, bself, atom, axis, baxis, world, bounds: blocker.cbounds, nbounds: blocker.cnbounds, abounds: blocker.bounds, iveHitSomething, blockers: blockers[axis.dname]})
111 | if (result === false) modResult = false
112 | if (result === "induce") modResult = result
113 | }
114 |
115 | if (modResult === "induce") return
116 |
117 | // SNAP to the surface!
118 | const newOffset = axis.front === axis.small? -bself[baxis.cutSmallName] : -baxis.size + bself[baxis.cutBigName]
119 | baxis.new = bbounds[axis.back] + newOffset
120 | const snapMovement = baxis.new - baxis.old
121 | axis.new = self[axis.name] + snapMovement
122 |
123 |
124 | if (iveHitSomething === true || modResult === false || modResult === "induce") continue
125 |
126 | // Change ACCELERATIONS!
127 | // Moving right or left
128 | if (axis === axes.dx) {
129 |
130 | // 2-way BOUNCE! I think this is the only 2-way collision resolution. I think...
131 | atom.nextdx *= 0.5
132 | atom.nextdx += self.dx/2
133 | transferToParent(atom, "nextdx", atom.nextdx)
134 | self.nextdx *= -0.5
135 | self.nextdx += atom.dx/2
136 |
137 | // Hardcoded trampoline override
138 | if (atom.bounce !== undefined && atom.turns % 2 !== 0) {
139 | self.nextdx = atom.bounce * -axis.direction/2
140 | }
141 | }
142 | else if (axis === axes.dy) {
143 |
144 | // Moving down
145 | if (axis.direction === 1) {
146 |
147 | // I'm on the ground!
148 | self.nextdy = atom.dy
149 | if (self.slip !== undefined) self.nextdx *= self.slip
150 | else self.nextdx *= UPDATE_MOVER_FRICTION
151 | self.grounded = true
152 | atom.jumpTick = 0
153 |
154 | // Hardcoded trampoline override
155 | if (atom.bounce !== undefined && atom.turns % 2 === 0) {
156 | self.nextdy = -atom.bounce
157 | self.nextdx *= 1.8
158 | }
159 |
160 | }
161 |
162 | // Moving up
163 | else {
164 |
165 | // Hit my head on something...
166 | self.nextdy = 0
167 | self.jumpTick = 0
168 |
169 | }
170 |
171 | }
172 |
173 | // Update other blocker infos maybe? nah they can do it!
174 | iveHitSomething = true
175 | iveReallyHitSomething = true
176 | }
177 |
178 | }
179 |
180 | //if (induce) sdhjds
181 |
182 | // Apply natural forces
183 | self.nextdy += UPDATE_MOVER_GRAVITY
184 | self.nextdx *= UPDATE_MOVER_AIR_RESISTANCE
185 |
186 | if (self.maxSpeed !== undefined) {
187 | const speed = Math.hypot(self.nextdx, self.nextdy)
188 | if (speed > self.maxSpeed) {
189 | const ratio = speed / self.maxSpeed
190 | self.nextdy /= ratio
191 | self.nextdx /= ratio
192 | }
193 | }
194 |
195 | // Now that I've checked all potential collisions, and corrected myself...
196 | // MOVE to the new position!
197 | self.x = axes.dx.new
198 | self.y = axes.dy.new
199 |
200 | // Update all children?
201 | updateAtomLinks(self)
202 |
203 | // DODGY FIX! Make sure everything is cut correctly by portals
204 | // Comment out these to uncover lots of bugs
205 | candidates.forEach(candidate => emergeCandidate(candidate, candidate.axes, getBounds(candidate.atom)))
206 | candidates.forEach(candidate => remergeCandidate(candidate))
207 |
208 | // Now that I've moved, I can safely rotate without messing anything else up!
209 | // ROTATE! (if there is enough room)
210 | if (self.nextturns !== 0) {
211 | turnAtom(self, self.nextturns, true, true, world)
212 | self.nextturns = 0
213 | }
214 |
215 | return iveReallyHitSomething
216 | }
217 |
218 | const makeAxesInfo = (x, y, dx, dy) => {
219 |
220 | const axes = {
221 | dx: {},
222 | dy: {},
223 | }
224 |
225 | // Variable Stuff
226 | axes.dx.new = x + dx
227 | axes.dx.value = dx
228 |
229 | axes.dy.new = y + dy
230 | axes.dy.value = dy
231 |
232 | // Static Stuff
233 | axes.dx.name = "x"
234 | axes.dx.dname = "dx"
235 | axes.dx.small = "left"
236 | axes.dx.big = "right"
237 | axes.dx.sizeName = "width"
238 | axes.dx.otherSizeName = "height"
239 | axes.dx.direction = dx >= 0? 1 : -1
240 | axes.dx.front = axes.dx.direction === 1? axes.dx.big : axes.dx.small
241 | axes.dx.back = axes.dx.front === axes.dx.big? axes.dx.small : axes.dx.big
242 | axes.dx.other = axes.dy
243 | axes.dx.cutFrontName = "cut" + axes.dx.front.as(Capitalised)
244 | axes.dx.cutBackName = "cut" + axes.dx.back.as(Capitalised)
245 | axes.dx.flingFrontName = axes.dx.direction === 1? "top" : "bottom"
246 | axes.dx.flingBackName = axes.dx.direction === 1? "bottom" : "top"
247 |
248 | axes.dy.name = "y"
249 | axes.dy.dname = "dy"
250 | axes.dy.small = "top"
251 | axes.dy.big = "bottom"
252 | axes.dy.sizeName = "height"
253 | axes.dy.otherSizeName = "width"
254 | axes.dy.direction = dy >= 0? 1 : -1
255 | axes.dy.front = axes.dy.direction === 1? axes.dy.big : axes.dy.small
256 | axes.dy.back = axes.dy.front === axes.dy.small? axes.dy.big : axes.dy.small
257 | axes.dy.other = axes.dx
258 | axes.dy.cutFrontName = "cut" + axes.dy.front.as(Capitalised)
259 | axes.dy.cutBackName = "cut" + axes.dy.back.as(Capitalised)
260 | axes.dy.flingFrontName = axes.dy.direction === 1? "left" : "right"
261 | axes.dy.flingBackName = axes.dy.direction === 1? "right" : "left"
262 |
263 | return axes
264 | }
265 |
266 |
267 | const makeCandidates = (self, axes) => {
268 | const atoms = getDescendentsAndMe(self)
269 | const candidates = atoms.map(atom => makeCandidate(atom, axes))
270 | return candidates
271 | }
272 |
273 | // TODO: should this support changes in cuts over time?
274 | // it can still be manually edited though! woo
275 | const makeCandidate = (atom, axes) => {
276 |
277 | //print(atom.fling)
278 |
279 | // This is what the new atom WOULD be after moving (if it doesn't hit anything)
280 | const natom = {
281 | ...atom,
282 | x: atom.x + axes.dx.value,
283 | y: atom.y + axes.dy.value,
284 | }
285 |
286 | if (atom.fling === 1) {
287 | natom.x = atom.x - axes.dy.value
288 | natom.y = atom.y + axes.dx.value
289 | }
290 |
291 | // Current bounds and new bounds
292 | const bounds = getBounds(atom)
293 | const nbounds = getBounds(natom)
294 |
295 | // Some axis-independent info to help me write code that works for all directions/axes
296 | const caxes = {
297 | dx: {},
298 | dy: {},
299 | }
300 |
301 | caxes.dx.old = atom.x
302 | caxes.dx.new = natom.x
303 | caxes.dx.size = atom.width
304 | caxes.dx.cutSmallName = "cutLeft"
305 | caxes.dx.cutBigName = "cutRight"
306 | caxes.dx.direction = caxes.dx.new > caxes.dx.old? 1 : -1
307 | caxes.dx.back = caxes.dx.direction === 1? "left" : "right"
308 | caxes.dx.front = caxes.dx.direction === 1? "right" : "left"
309 | caxes.dx.cutBackName = "cut" + caxes.dx.back.as(Capitalised)
310 |
311 | caxes.dy.old = atom.y
312 | caxes.dy.new = natom.y
313 | caxes.dy.size = atom.height
314 | caxes.dy.cutSmallName = "cutTop"
315 | caxes.dy.cutBigName = "cutBottom"
316 | caxes.dy.direction = caxes.dy.new > caxes.dy.old? 1 : -1
317 | caxes.dy.back = caxes.dy.direction === 1? "top" : "bottom"
318 | caxes.dy.front = caxes.dy.direction === 1? "bottom" : "top"
319 | caxes.dy.cutBackName = "cut" + caxes.dy.back.as(Capitalised)
320 |
321 | // Put it all together...
322 | const candidate = {
323 | atom,
324 | bounds,
325 | nbounds,
326 | axes: caxes,
327 | }
328 |
329 | return candidate
330 |
331 | }
332 |
333 | // This cleans up all the merge bugs that exist lol
334 | /*const autoMergeCandidate = (candidate) => {
335 | const catom = candidate.atom
336 | for (const key in catom.portals) {
337 | const portal = catom.portals[key]
338 | if (portal === undefined) continue
339 | const cutName = "cut" + key.as(Capitalised)
340 | const back = key
341 | const front = getOppositeSideName(back)
342 | const cbounds = getBounds(catom)
343 | const pbounds = getBounds(portal)
344 | if (cbounds[back] === pbounds[front]) continue
345 | const gap =
346 | catom[cutName] -= cbounds[back] - pbounds[front]
347 | }
348 | }*/
349 |
350 | const remergeCandidate = (candidate) => {
351 | const catom = candidate.atom
352 | for (const key in catom.portals) {
353 | const portal = catom.portals[key]
354 | if (portal === undefined) continue
355 | const cutName = "cut" + key.as(Capitalised)
356 | const back = key
357 | const front = getOppositeSideName(back)
358 | const cbounds = getBounds(catom)
359 | const pbounds = getBounds(portal)
360 | if (cbounds[back] === pbounds[front]) continue
361 | const gap = cbounds[back] - pbounds[front]
362 |
363 | const direction = (key === "bottom" || key === "right")? 1 : -1
364 | catom[cutName] += gap * direction
365 | }
366 | }
367 |
368 | const emergeCandidate = (candidate, axes, nbounds = candidate.bounds) => {
369 |
370 | const atom = candidate.atom
371 | // Go through each of my portals...
372 | for (const key in atom.portals) {
373 | const portal = atom.portals[key]
374 | if (portal === undefined) continue
375 | const pbounds = getBounds(portal)
376 |
377 | //print(atom.id, key)
378 |
379 | // I'm only interested in my BACK because I'm EMERGING!
380 | for (const axis of axes) {
381 | if (axis.back !== key) continue
382 |
383 | // What's the distance between me and the portal?
384 | const distanceToPortal = nbounds[axis.back] - pbounds[axis.front]
385 |
386 | // Reduce my cut so that there isn't a gap anymore!
387 | atom[axis.cutBackName] -= distanceToPortal * axis.direction
388 |
389 | // Fire moddable events :)
390 | if (portal.portal.move !== undefined) portal.portal.move()
391 | if (portal.portal.moveIn !== undefined) portal.portal.moveIn()
392 |
393 | // If I'm fully emerged, then I'm finished with this portal!
394 | if (atom[axis.cutBackName] <= 0) {
395 | atom.portals[axis.back] = undefined
396 | atom[axis.cutBackName] = 0
397 | if (portal.portal.exit !== undefined) portal.portal.exit({froggy: atom})
398 | }
399 |
400 | }
401 | }
402 |
403 | // (manually) Update the candidate info (with the new cut)!!!
404 | const natom = {
405 | ...atom,
406 | x: candidate.axes.dx.new,
407 | y: candidate.axes.dy.new,
408 | }
409 | candidate.nbounds = getBounds(natom)
410 |
411 | }
412 |
413 | const getBlockers = (candidate, axes) => {
414 |
415 | const blockers = {dx: [], dy: []}
416 |
417 | for (const axis of axes) {
418 |
419 | if (candidate.atom.world === undefined) continue
420 | if (candidate.atom.world.atoms === undefined) continue
421 |
422 | const cself = candidate.atom
423 | for (const atom of cself.world.atoms) {
424 |
425 | if (atomIsDescendant(cself, atom)) continue
426 | if (atomIsDescendant(atom, cself)) continue
427 | if (atom.isVisual) continue
428 | if (atom === cself) continue
429 |
430 | // Check here for collisions with the inside edge of portals (the wrong way)
431 | if (cself.portals[axis.front] !== undefined) {
432 | const portal = cself.portals[axis.front]
433 | if (atomIsDescendant(atom, portal)) {
434 | continue
435 | }
436 | }
437 |
438 | const abounds = getBounds(atom)
439 | const bounds = candidate.bounds
440 | const nbounds = candidate.nbounds
441 |
442 | // Do I go PAST this atom?
443 | const startsInFront = bounds[axis.front]*axis.direction <= abounds[axis.back]*axis.direction
444 | const endsThrough = nbounds[axis.front]*axis.direction >= abounds[axis.back]*axis.direction
445 | if (!startsInFront || !endsThrough) continue
446 |
447 | // Do I actually BUMP into this atom? (ie: I don't go to the side of it)
448 | let bumps = true
449 | const otherAxes = axes.values().filter(a => a !== axis)
450 | for (const other of otherAxes) {
451 | const reach = [bounds[other.small], bounds[other.big]]
452 | const nreach = [nbounds[other.small], nbounds[other.big]]
453 | const areach = [abounds[other.small], abounds[other.big]]
454 | if (!aligns(reach, nreach, areach)) bumps = false
455 | }
456 | if (!bumps) continue
457 |
458 | // Work out the distance to this atom we would crash into
459 | // We don't care about it if we already found a NEARER one to crash into :)
460 | const distance = (abounds[axis.back] - bounds[axis.front]) * axis.direction
461 | blockers[axis.dname].push({atom, bounds: abounds, distance, cbounds: bounds, cnbounds: nbounds, candidate})
462 | }
463 | }
464 |
465 | return blockers
466 | }
467 |
468 | // Orders blockers in-place
469 | const orderBlockers = (blockers, axes) => {
470 |
471 | // Order the blockers so that portals gets processed LAST
472 | // (because they don't really block, do they)
473 | for (const axis of axes) {
474 |
475 | const winningBlockers = new Map()
476 |
477 | for (const blocker of blockers[axis.dname]) {
478 | if (!winningBlockers.has(blocker.candidate)) {
479 | winningBlockers.set(blocker.candidate, [blocker])
480 | continue
481 | }
482 | const winners = winningBlockers.get(blocker.candidate)
483 | if (winners[0].distance > blocker.distance) {
484 | winningBlockers.set(blocker.candidate, [blocker])
485 | }
486 | else if (winners[0].distance === blocker.distance) {
487 | winners.push(blocker)
488 | }
489 | }
490 |
491 | blockers[axis.dname] = [...winningBlockers.values()].flat()
492 |
493 | blockers[axis.dname].sort((a, b) => {
494 | //if (a.distance < b.distance) return -1
495 | //if (a.distance > b.distance) return 1
496 | if (a.atom.isPortal && !b.atom.isPortal) return 1
497 | if (!a.atom.isPortal && b.atom.isPortal) return -1
498 | return 0
499 | })
500 |
501 | }
502 |
503 | return blockers
504 | }
--------------------------------------------------------------------------------
/source/multiverse.js:
--------------------------------------------------------------------------------
1 | const URL_QUERY = new URLSearchParams(window.location.search)
2 | let SPEED_MOD = URL_QUERY.has("speed")? parseFloat(URL_QUERY.get("speed")) : 1
3 | let PAUSED = URL_QUERY.has("paused")? URL_QUERY.get("paused").as(Boolean) : false
4 | let FROGGY_BOUNDS = URL_QUERY.has("bounds")? URL_QUERY.get("bounds").as(Boolean) : false
5 | let INVERT = URL_QUERY.has("invert")? URL_QUERY.get("invert").as(Boolean) : false
6 | let ONION_SKIN = URL_QUERY.has("onion")? parseInt(URL_QUERY.get("onion")) : 0
7 | let TRAIL_LENGTH = URL_QUERY.has("trail")? parseInt(URL_QUERY.get("trail")) : 0
8 | let EXPERIMENT_ID = URL_QUERY.has("experiment")? URL_QUERY.get("experiment") : ""
9 | let PORTAL_TYPE_ID = URL_QUERY.has("portal")? URL_QUERY.get("portal").as(UpperCase) : "MOVE"
10 | let BONUS_ID = URL_QUERY.has("bonus")? URL_QUERY.get("bonus") : ""
11 | let MENU_ID = URL_QUERY.has("menu")? URL_QUERY.get("menu") : undefined
12 | let NO_FROG_SPAWN_ID = URL_QUERY.has("nofrog")? URL_QUERY.get("nofrog").as(Boolean) : false
13 | let STEP = false
14 |
15 | //=======//
16 | // Setup //
17 | //=======//
18 | const makeMultiverse = () => {
19 | const multiverse = {}
20 | multiverse.worlds = []
21 | multiverse.void = {atoms: []}
22 | const world = makeWorld()
23 | addWorld(multiverse, world)
24 |
25 | // Menu
26 | if (MENU_ID === undefined) {
27 | addMenuElement(ELEMENT_FROG, multiverse)
28 | addMenuElement(ELEMENT_BOX, multiverse)
29 | addMenuElement(ELEMENT_LEAF, multiverse)
30 | addMenuElement(ELEMENT_PLATFORM, multiverse)
31 | addMenuElement(ELEMENT_LILYPAD, multiverse)
32 | addMenuElement(ELEMENT_POTION_ROTATE, multiverse)
33 | addMenuElement(ELEMENT_PORTAL_MOVE, multiverse, ELEMENT_SPAWNER_PORTAL, "Portal")
34 | addMenuElement(ELEMENT_PORTAL_PASTNOWLINE, multiverse, ELEMENT_SPAWNER_PORTAL, "Pastnowlinal")
35 | addMenuElement(ELEMENT_PORTAL_PASTLINE, multiverse, ELEMENT_SPAWNER_PORTAL, "Pastlinal")
36 | addMenuElement(ELEMENT_PORTAL_FUTURELINE, multiverse, ELEMENT_SPAWNER_PORTAL, "Pastlinal")
37 | addMenuElement(ELEMENT_PORTAL_DIMENSION, multiverse, ELEMENT_SPAWNER_PORTAL, "Dimensial")
38 | addMenuElement(ELEMENT_PORTAL_PASTNOW, multiverse, ELEMENT_SPAWNER_PORTAL)
39 | //addMenuElement(ELEMENT_BOX_DOUBLE, multiverse)
40 | }
41 | else if (MENU_ID === "yellow") {
42 | addMenuElement(ELEMENT_FROG_YELLOW, multiverse)
43 | addMenuElement(ELEMENT_BOX, multiverse)
44 | addMenuElement(ELEMENT_PLATFORM, multiverse)
45 | addMenuElement(ELEMENT_LILYPAD, multiverse)
46 | addMenuElement(ELEMENT_PORTAL_MOVE, multiverse, ELEMENT_SPAWNER_PORTAL, "Portal")
47 | addMenuElement(ELEMENT_PORTAL_FUTURENOW, multiverse, ELEMENT_SPAWNER_PORTAL)
48 | addMenuElement(ELEMENT_PORTAL_PASTNOW, multiverse, ELEMENT_SPAWNER_PORTAL)
49 | }
50 | else if (MENU_ID === "cyan") {
51 | addMenuElement(ELEMENT_FROG_CYAN, multiverse)
52 | addMenuElement(ELEMENT_BOX, multiverse)
53 | addMenuElement(ELEMENT_PLATFORM, multiverse)
54 | addMenuElement(ELEMENT_LILYPAD, multiverse)
55 | }
56 | else if (MENU_ID === "purple") {
57 | addMenuElement(ELEMENT_FROG_PURPLE, multiverse)
58 | addMenuElement(ELEMENT_BOX, multiverse)
59 | addMenuElement(ELEMENT_PLATFORM, multiverse)
60 | addMenuElement(ELEMENT_LILYPAD, multiverse)
61 | }
62 | else if (MENU_ID === "green") {
63 | addMenuElement(ELEMENT_FROG_GREEN, multiverse)
64 | addMenuElement(ELEMENT_BOX, multiverse)
65 | addMenuElement(ELEMENT_PLATFORM, multiverse)
66 | addMenuElement(ELEMENT_LILYPAD, multiverse)
67 | addMenuElement(ELEMENT_PORTAL_MOVE, multiverse, ELEMENT_SPAWNER_PORTAL, "Portal")
68 | if (BONUS_ID === "slow") {
69 | const slowverse = makeWorld()
70 | slowverse.isSlow = true
71 | addWorld(multiverse, slowverse)
72 | }
73 | else if (BONUS_ID === "rewind") {
74 | const slowverse = makeWorld()
75 | slowverse.isReverse = true
76 | addWorld(multiverse, slowverse)
77 | }
78 | }
79 | else if (MENU_ID === "black") {
80 | addMenuElement(ELEMENT_FROG_BLACK, multiverse)
81 | addMenuElement(ELEMENT_PORTAL_REFROG, multiverse, ELEMENT_SPAWNER_PORTAL)
82 | //addMenuElement(ELEMENT_BOX, multiverse)
83 | //addMenuElement(ELEMENT_PLATFORM, multiverse)
84 | //addMenuElement(ELEMENT_LILYPAD, multiverse)
85 | }
86 | else if (MENU_ID === "rainbow") {
87 | addMenuElement(ELEMENT_FROG, multiverse)
88 | addMenuElement(ELEMENT_FROG_YELLOW, multiverse)
89 | addMenuElement(ELEMENT_FROG_CYAN, multiverse)
90 | addMenuElement(ELEMENT_FROG_PURPLE, multiverse)
91 | addMenuElement(ELEMENT_FROG_GREEN, multiverse)
92 | //addMenuElement(ELEMENT_FROG_BLACK, multiverse)
93 | }
94 |
95 | return multiverse
96 | }
97 |
98 | const menu = {atoms: [], x: 0}
99 | const addMenuElement = (element, multiverse, menuElement = ELEMENT_SPAWNER) => {
100 | menu.x += 25
101 | const atom = makeAtom({
102 | ...element,
103 | ...menuElement,
104 | spawn: element,
105 | x: menu.x,
106 | isMenuItem: true,
107 | })
108 |
109 | const remainingY = MENU_HEIGHT - atom.height
110 | atom.y = Math.round(remainingY/2)
111 |
112 | addAtom(multiverse.void, atom)
113 |
114 | menu.x += atom.width
115 | }
116 |
117 | const makeMultiverseCanvas = (multiverse) => {
118 | const stage = Stage.make()
119 | stage.tick = () => {}
120 | const {context, canvas} = stage
121 | canvas.style["background-color"] = INVERT? Colour.Grey : Colour.Black
122 | //canvas.style["image-rendering"] = "pixelated"
123 | on.resize(() => {
124 | const multiverseHeight = getMultiverseHeight(multiverse, canvas)
125 | const height = Math.max(multiverseHeight, document.body.clientHeight)
126 | canvas.height = height
127 | canvas.style["height"] = height
128 | canvas.width = document.body.clientWidth
129 | canvas.style["width"] = document.body.clientWidth
130 | requestAnimationFrame(() => drawMultiverse(multiverse, context))
131 | })
132 |
133 | setInterval(() => tickMultiverse(multiverse, context), 1000/SPEED_MOD / 60)
134 | return canvas
135 | }
136 |
137 | //=====//
138 | // API //
139 | //=====//
140 | let worldCount = 0
141 | const addWorld = (multiverse, world) => {
142 | multiverse.worlds.push(world)
143 | world.id = worldCount++
144 | trigger("resize")
145 | }
146 |
147 | const removeWorld = (multiverse, world) => {
148 | const id = multiverse.worlds.indexOf(world)
149 | multiverse.worlds.splice(id, 1)
150 | trigger("resize")
151 | }
152 |
153 |
154 | const replaceWorld = (target, source) => {
155 | const tid = multiverse.worlds.indexOf(target)
156 | const sid = multiverse.worlds.indexOf(source)
157 | if (tid !== -1) multiverse.worlds[tid] = source
158 | if (sid !== -1) multiverse.worlds[sid] = target
159 | }
160 |
161 | //===========//
162 | // Game Loop //
163 | //===========//
164 | const tickMultiverse = (multiverse, context) => {
165 | if (STEP) STEP = false
166 | else if (PAUSED) {
167 | updateCursor(multiverse, context)
168 | drawMultiverse(multiverse, context)
169 | return
170 | }
171 |
172 | updateCursor(multiverse, context)
173 | updateMultiverse(multiverse)
174 | drawMultiverse(multiverse, context)
175 | }
176 |
177 | const hand = {
178 | atom: undefined,
179 | source: undefined,
180 | offset: {x: undefined, y: undefined},
181 | previous: {x: undefined, y: undefined}
182 | }
183 |
184 | let handStarting = {x: 0, y: 0}
185 | const CURSOR_SQUEEZE_EFFORT = 100
186 | const updateCursor = (multiverse, context) => {
187 | const [cx, cy] = Mouse.position
188 | const [mx, my] = [cx + scrollX, cy + scrollY]
189 | const down = Mouse.Left
190 | const address = getAddress(mx, my, multiverse, context)
191 | const {world, x, y} = address
192 |
193 | // State: EMPTY HAND
194 | if (hand.atom === undefined) {
195 |
196 | if (down) {
197 | for (const atom of world.atoms) {
198 | if (pointOverlaps({x, y}, atom)) {
199 | const {grab} = atom
200 | hand.offset = {x: atom.x-x, y: atom.y-y}
201 | const grabbed = grab(atom, hand, world)
202 | hand.atom = grabbed
203 | if (grabbed !== undefined) {
204 | hand.source = world
205 | hand.previous = {x: mx, y: my}
206 | handStarting.x = mx
207 | handStarting.y = my
208 | }
209 | }
210 | }
211 | }
212 |
213 | }
214 |
215 | // State: HOLDING SOMETHING
216 | else {
217 |
218 | // Move it to the dragged position!
219 | //hand.atom.prevBounds = getBounds({...hand.atom})
220 | moveAtom(hand.atom, x + hand.offset.x, y + hand.offset.y)
221 | if (hand.atom.flipX !== undefined) {
222 | const mdx = mx - hand.previous.x
223 | const oldX = hand.atom.x
224 | if (!hand.atom.flipX && mdx > 1) flipAtom(hand.atom)
225 | else if (hand.atom.flipX && mdx < -1) flipAtom(hand.atom)
226 | const newX = hand.atom.x
227 | hand.offset.x += newX-oldX
228 | }
229 | //REAL
230 | hand.atom.dx = (mx - hand.previous.x)
231 | hand.atom.dy = (my - hand.previous.y)
232 |
233 | //DEBUG
234 | /*hand.atom.dx = (mx - handStarting.x) / 10
235 | hand.atom.dy = (my - handStarting.y) / 10*/
236 |
237 | hand.atom.nextdx = hand.atom.dx
238 | hand.atom.nextdy = hand.atom.dy
239 | hand.atom.jumpTick = 0
240 |
241 | /*for (const link of hand.atom.links) {
242 | link.atom.dx = (mx - hand.previous.x)
243 | link.atom.dy = (my - hand.previous.y)
244 | link.atom.nextdx = link.atom.dx
245 | link.atom.nextdy = link.atom.dy
246 | link.jumpTick = 0
247 | }*/
248 |
249 | // Transfer the dragged atom to another world if needed
250 | if (world !== hand.source) {
251 | /*hand.source.atoms = hand.source.atoms.filter(atom => atom !== hand.atom)
252 | world.atoms.push(hand.atom)*/
253 | moveAtomWorld(hand.atom, hand.source, world)
254 | hand.source = world
255 | }
256 |
257 | // Are we letting go of the atom?
258 | if (!down) {
259 |
260 | // Can we actually drop it here?
261 | let canDrop = true
262 | for (const atom of world.atoms) {
263 | if (atom === hand.atom) continue
264 | if (atomOverlaps(hand.atom, atom)) {
265 | canDrop = false
266 | break
267 | }
268 | }
269 |
270 | // If there's room, drop it!
271 | if (canDrop) {
272 | hand.atom.refrogTrack = undefined
273 | hand.atom = undefined
274 | }
275 |
276 | for (const world of multiverse.worlds) {
277 | saveFutureProjection(world)
278 | }
279 |
280 | }
281 |
282 | // Help keep track of hand speed
283 | hand.previous = {x: mx, y: my}
284 |
285 | }
286 |
287 | }
288 |
289 | const updateMultiverse = (multiverse) => {
290 | if (hand.atom !== undefined) return
291 | updateWorldLinks(multiverse.void)
292 | for (const world of multiverse.worlds) {
293 | updateWorld(world)
294 | }
295 | for (const world of multiverse.worlds) {
296 | prepWorld(world)
297 | }
298 | }
299 |
300 | const drawMultiverse = (multiverse, context) => {
301 | context.clearRect(0, 0, canvas.width, canvas.height)
302 | drawWorld(multiverse.void, context, false)
303 |
304 | let x = 0
305 | let y = 0
306 |
307 | context.translate(0, MENU_HEIGHT)
308 | for (let i = 0; i < multiverse.worlds.length; i++) {
309 | const world = multiverse.worlds[i]
310 | if (world.isHidden) continue
311 | drawWorld(world, context)
312 | if (i >= multiverse.worlds.length-1) break
313 | x += WORLD_WIDTH
314 | context.translate(WORLD_WIDTH, 0)
315 | if (x + WORLD_WIDTH >= context.canvas.width) {
316 | x = 0
317 | y += WORLD_HEIGHT
318 | context.resetTransform()
319 | context.translate(0, y + MENU_HEIGHT)
320 | }
321 | }
322 | context.resetTransform()
323 | }
324 |
325 | //=========//
326 | // Usefuls //
327 | //=========//
328 | const getAddress = (mx, my, multiverse) => {
329 | const column = Math.floor(mx / WORLD_WIDTH)
330 | const row = Math.floor((my-MENU_HEIGHT) / WORLD_HEIGHT)
331 | const world = getWorldFromGridPosition(column, row, multiverse, canvas)
332 | if (world === multiverse.void) return {world, x: mx, y: my}
333 | const x = mx - column*WORLD_WIDTH
334 | const y = my-MENU_HEIGHT - row*WORLD_HEIGHT
335 | return {world, x, y}
336 | }
337 |
338 | const getWorldFromGridPosition = (column, row, multiverse, canvas) => {
339 | const columns = getGridMaxWidth(canvas)
340 | if (column >= columns) return multiverse.void
341 | const world = multiverse.worlds[row*columns + column]
342 | if (world === undefined) return multiverse.void
343 | return world
344 | }
345 |
346 | const getGridMaxWidth = (canvas) => Math.floor(canvas.width / WORLD_WIDTH)
347 |
348 | const getMultiverseHeight = (multiverse, canvas) => {
349 | let x = 0
350 | let y = 0
351 | for (let i = 0; i < multiverse.worlds.length-1; i++) {
352 | x += WORLD_WIDTH
353 | if (x + WORLD_WIDTH >= canvas.width) {
354 | x = 0
355 | y += WORLD_HEIGHT
356 | }
357 | }
358 | return y + WORLD_HEIGHT + MENU_HEIGHT
359 | }
--------------------------------------------------------------------------------
/source/world.js:
--------------------------------------------------------------------------------
1 | //=======//
2 | // Setup //
3 | //=======//
4 | let hasMadeFirstWorld = false
5 | const makeWorld = ({isProjection = false} = {}) => {
6 | const world = {}
7 | const top = makeAtom(ELEMENT_VOID)
8 | const bottom = makeAtom({...ELEMENT_VOID, y: WORLD_HEIGHT-ELEMENT_VOID.height})
9 | const left = makeAtom({...ELEMENT_VOID, turns: 1})
10 | const right = makeAtom({...ELEMENT_VOID, turns: 1, x: WORLD_WIDTH-ELEMENT_VOID.height})
11 | world.atoms = [top, bottom, left, right]
12 |
13 | world.pastProjections = []
14 | world.isProjection = isProjection
15 |
16 | if (hasMadeFirstWorld) return world
17 | hasMadeFirstWorld = true
18 |
19 | if (EXPERIMENT_ID === "empty") {
20 |
21 | }
22 | else if (EXPERIMENT_ID === "simpleport") {
23 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 360}))
24 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 150}))
25 | }
26 | else if (EXPERIMENT_ID === "fling") {
27 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 100, flipX: true}))
28 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
29 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
30 | /*
31 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 400, y: 360}))
32 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 150, turns: 1}))
33 | */
34 |
35 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 460}))
36 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 360}))
37 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 400, y: 150, turns: 1}))
38 |
39 | }
40 | else if (EXPERIMENT_ID === "pastlinefling") {
41 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 100, flipX: true}))
42 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
43 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
44 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 100, y: 460}))
45 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 400, y: 150, turns: 1}))
46 | }
47 | else if (EXPERIMENT_ID === "futurelinefling") {
48 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 150, flipX: true}))
49 | addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 100, flipX: true}))
50 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
51 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
52 | addAtom(world, makeAtom({...ELEMENT_PORTAL_FUTURELINE, x: 100, y: 460}))
53 | addAtom(world, makeAtom({...ELEMENT_PORTAL_FUTURELINE, x: 400, y: 150, turns: 1}))
54 |
55 | }
56 | else if (EXPERIMENT_ID === "pastnowlinefling") {
57 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 90, flipX: true}))
58 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 50, flipX: true}))
59 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
60 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
61 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 105, y: 460}))
62 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 450, y: 180, turns: 1}))
63 |
64 | }
65 | else if (EXPERIMENT_ID === "pastnowfling") {
66 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 90, flipX: true}))
67 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 50, flipX: true}))
68 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
69 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
70 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOW, x: 100, y: 460}))
71 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOW, x: 400, y: 150, turns: 1}))
72 |
73 | }
74 | else if (EXPERIMENT_ID === "pastnow") {
75 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 90, flipX: true}))
76 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 50, flipX: true}))
77 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
78 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
79 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOW, x: 100, y: 460}))
80 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOW, x: 310, y: 150, turns: 0}))
81 |
82 | }
83 | else if (EXPERIMENT_ID === "pastnowline") {
84 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 90, flipX: true}))
85 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 50, flipX: true}))
86 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
87 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
88 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 100, y: 460}))
89 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 310, y: 150, turns: 0}))
90 |
91 | }
92 | else if (EXPERIMENT_ID === "pastnowlinebounce") {
93 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 75, y: 90, flipX: true}))
94 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 450, y: 50, flipX: true}))
95 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 250, flipX: true}))
96 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 400, flipX: true}))
97 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 50, y: 460}))
98 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTNOWLINE, x: 220, y: 300, turns: 0}))
99 | addAtom(world, makeAtom({...ELEMENT_LILYPAD, x: 312, y: 475}))
100 |
101 | }
102 | else if (EXPERIMENT_ID === "simplepastline") {
103 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 100, y: 360}))
104 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 300, y: 150}))
105 | }
106 | else if (EXPERIMENT_ID === "simpledimension") {
107 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 100, y: 360}))
108 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 300, y: 150}))
109 | }
110 | else if (EXPERIMENT_ID === "simplefuture") {
111 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 100, flipX: true}))
112 | addAtom(world, makeAtom({...ELEMENT_PORTAL_FUTURELINE, x: 100, y: 360}))
113 | addAtom(world, makeAtom({...ELEMENT_PORTAL_FUTURELINE, x: 300, y: 150}))
114 | }
115 | else if (EXPERIMENT_ID === "freefall") {
116 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 160}))
117 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 300}))
118 | }
119 | else if (EXPERIMENT_ID === "headpoke") {
120 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 360}))
121 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 150}))
122 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 175, y: 380, flipX: false}))
123 | }
124 | else if (EXPERIMENT_ID === "jumpright") {
125 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 182, y: 320, turns: 1}))
126 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 350, y: 320, turns: 1}))
127 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 100, y: 380, flipX: true}))
128 | }
129 | else if (EXPERIMENT_ID === "dimensionright") {
130 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 178, y: 330, turns: 1}))
131 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 350, y: 330, turns: 1}))
132 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 100, y: 380, flipX: true}))
133 |
134 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 205, y: 125}))
135 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 205, y: 250}))
136 | }
137 |
138 | else if (EXPERIMENT_ID === "pastlineright") {
139 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 178, y: 330, turns: 1}))
140 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 350, y: 330, turns: 1}))
141 | addAtom(world, makeAtom({...ELEMENT_FROG, x: 100, y: 380, flipX: true}))
142 |
143 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 205, y: 125}))
144 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 205, y: 250}))
145 | }
146 | else if (EXPERIMENT_ID === "dimensionfall") {
147 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 300, y: 160}))
148 | addAtom(world, makeAtom({...ELEMENT_PORTAL_DIMENSION, x: 300, y: 300}))
149 | }
150 | else if (EXPERIMENT_ID === "pastlinefall") {
151 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 300, y: 160}))
152 | addAtom(world, makeAtom({...ELEMENT_PORTAL_PASTLINE, x: 300, y: 300}))
153 | }
154 | else if (EXPERIMENT_ID === "gfling") {
155 | const ptype = PORTAL_TYPE_ID
156 | const pelement = eval("ELEMENT_PORTAL_" + ptype)
157 | const felement = eval("ELEMENT_FROG_" + MENU_ID.as(UpperCase))
158 | if (!NO_FROG_SPAWN_ID) addAtom(world, makeAtom({...felement, x: 130, y: 90, flipX: true}))
159 |
160 | addAtom(world, makeAtom({...pelement, x: 100, y: 460}))
161 | addAtom(world, makeAtom({...pelement, x: 400, y: 150, turns: 1}))
162 | }
163 | else if (EXPERIMENT_ID === "gfall") {
164 | const ptype = PORTAL_TYPE_ID
165 | const pelement = eval("ELEMENT_PORTAL_" + ptype)
166 | const felement = eval("ELEMENT_FROG_" + MENU_ID.as(UpperCase))
167 | if (!NO_FROG_SPAWN_ID) addAtom(world, makeAtom({...felement, x: 130, y: 90, flipX: true}))
168 | addAtom(world, makeAtom({...pelement, x: 300, y: 160}))
169 | addAtom(world, makeAtom({...pelement, x: 300, y: 300}))
170 | }
171 | else if (EXPERIMENT_ID === "gright") {
172 | const ptype = PORTAL_TYPE_ID
173 | const pelement = eval("ELEMENT_PORTAL_" + ptype)
174 | const felement = eval("ELEMENT_FROG_" + MENU_ID.as(UpperCase))
175 |
176 | addAtom(world, makeAtom({...pelement, x: 182, y: 320, turns: 1}))
177 | addAtom(world, makeAtom({...pelement, x: 350, y: 320, turns: 1}))
178 | if (!NO_FROG_SPAWN_ID)addAtom(world, makeAtom({...felement, x: 100, y: 380, flipX: true}))
179 | }
180 | else if (EXPERIMENT_ID === "gsimple") {
181 | const ptype = PORTAL_TYPE_ID
182 | const pelement = eval("ELEMENT_PORTAL_" + ptype)
183 | const felement = eval("ELEMENT_FROG_" + MENU_ID.as(UpperCase))
184 | if (!NO_FROG_SPAWN_ID) addAtom(world, makeAtom({...felement, x: 130, y: 90, flipX: true}))
185 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 20, y: 90, flipX: true}))
186 | addAtom(world, makeAtom({...pelement, x: 100, y: 360}))
187 | addAtom(world, makeAtom({...pelement, x: 300, y: 150}))
188 | }
189 | else if (EXPERIMENT_ID === "ggen") {
190 | const ptype = PORTAL_TYPE_ID
191 | const pelement = eval("ELEMENT_PORTAL_" + ptype)
192 | const felement = eval("ELEMENT_FROG_" + MENU_ID.as(UpperCase))
193 | if (!NO_FROG_SPAWN_ID) addAtom(world, makeAtom({...felement, x: 20, y: 90, flipX: true}))
194 | //addAtom(world, makeAtom({...ELEMENT_LEAF, x: 20, y: 90, flipX: true}))
195 | addAtom(world, makeAtom({...ELEMENT_LILYPAD, x: 100, y: 470}))
196 | addAtom(world, makeAtom({...pelement, x: 210, y: 460}))
197 | addAtom(world, makeAtom({...pelement, x: 450, y: 150, turns: 1}))
198 | }
199 | else {
200 | // PORTAL FLING 1
201 | /*addAtom(world, makeAtom({...ELEMENT_FROG, x: 130, y: 200, flipX: false}))
202 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 135, y: 380, flipX: false}))
203 | addAtom(world, makeAtom({...ELEMENT_LILYPAD, x: 120, y: 475, flipX: false}))
204 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 350, turns: 0}))
205 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 400, y: 150, turns: 1}))
206 | */
207 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 260, y: 440, flipX: true}))
208 | //addAtom(world, makeAtom({...ELEMENT_FROG, x: 380, y: 440, flipX: false}))
209 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 350, y: 320, turns: 3}))
210 | //addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 340, y: 240}))
211 |
212 |
213 | // CHAOTIC TEST
214 | /*addAtom(world, makeAtom({...ELEMENT_FROG, x: 120, y: 200, turns: 1}))
215 | //addAtom(world, makeAtom({...ELEMENT_FROG, y: 100, x: 180}))
216 | //addAtom(world, makeAtom({...ELEMENT_FROG, y: 50, x: 340}))
217 | //addAtom(world, makeAtom({...ELEMENT_FROG, y: 400}))
218 |
219 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 100, y: 400}))
220 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 160}))
221 |
222 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 300, y: 240}))
223 | addAtom(world, makeAtom({...ELEMENT_PORTAL_MOVE, x: 450, y: 350, turns: 1}))
224 |
225 | addAtom(world, makeAtom({...ELEMENT_PLATFORM, x: 290, y: 170}))*/
226 |
227 | }
228 |
229 |
230 | return world
231 | }
232 |
233 | //=========//
234 | // Usefuls //
235 | //=========//
236 | const LINKED_PROPERTIES = [
237 | "width",
238 | "height",
239 | //"cutTop",
240 | //"cutBottom",
241 | //"cutRight",
242 | //"cutLeft",
243 | "x",
244 | "y",
245 | "dx",
246 | "dy",
247 | "nextdx",
248 | "nextdy",
249 | "turns",
250 | "nextturns",
251 | "flipX",
252 | ]
253 |
254 | // TODO!!! These should allow you to NOT bring over specific links. Add a parameter and/or special link properties to cater to this.
255 | const addAtom = (world, atom, {ignoreLinks = false} = {}) => {
256 | world.atoms.push(atom)
257 | atom.world = world // I give up. Lets use state... :(
258 | if (!ignoreLinks) {
259 | for (const link of atom.links) {
260 | addAtom(world, link.atom)
261 | }
262 | }
263 | //atom.prevBounds = getBounds(atom)
264 | updateAtomLinks(atom)
265 | }
266 |
267 | const removeAtom = (world, atom, {includingChildren = true, destroy = false} = {}) => {
268 |
269 | world.atoms = world.atoms.filter(a => a !== atom)
270 |
271 | if (destroy) {
272 | if (atom.parent !== undefined) {
273 | atom.parent.links = atom.parent.links.filter(link => link.atom !== atom)
274 | }
275 |
276 | for (const link of atom.links) {
277 | link.atom.parent = undefined
278 | if (link.atom.onPromote !== undefined) link.atom.onPromote(link.atom)
279 | }
280 | }
281 |
282 | if (includingChildren) {
283 |
284 | if (atom === undefined) throw new Error("needed error")
285 | for (const link of atom.links) {
286 | removeAtom(world, link.atom, {destroy})
287 | }
288 | }
289 | }
290 |
291 | const moveAtomWorld = (atom, world, nworld) => {
292 | removeAtom(world, atom)
293 | addAtom(nworld, atom)
294 | }
295 |
296 | const numberAtoms = (atoms) => {
297 | let i = 0
298 | for (const atom of atoms) {
299 | atom.atom_id = i++
300 | }
301 | }
302 |
303 | // THIS FUNCTION IS TOTALLY BROKEN
304 | // AVOID AT ALL COSTS
305 | // target (for portals)
306 | // parent
307 | // links link.atom
308 | // portals.top left right bottom
309 | const betterCloneAtoms = (atoms, new_world) => {
310 |
311 | // Number stuff relatively speaking
312 | numberAtoms(atoms)
313 | const cloned_atoms = []
314 | for (const atom of atoms) {
315 | const cloned_atom = {atom_id: atom.atom_id}
316 | cloned_atoms[atom.atom_id] = cloned_atom
317 | }
318 |
319 | for (const atom of atoms) {
320 | const cloned_atom = cloned_atoms[atom.atom_id]
321 | for (const key in atom) {
322 | if (key === "atom_id") {
323 | continue
324 | }
325 | if (key === "world") {
326 | cloned_atom.world = new_world
327 | continue
328 | }
329 | if (key === "id") {
330 | cloned_atom.id = atom.id //JUST FOR DEBUGGING NOT FOR ANYTHING ELSE //HAHA I USED IT FOR SOMETHING ELSE WHAT A BAD IDEA
331 | continue
332 | }
333 | if (key === "target" || key === "parent") {
334 | if (atoms.includes(atom[key])) {
335 | cloned_atom[key] = cloned_atoms[atom[key].atom_id]
336 | continue
337 | }
338 | const a = atom[key]
339 | if (a === undefined) {
340 | cloned_atom[key] = undefined
341 | continue
342 | }
343 | if (!a.world.atoms.includes(a)) {
344 | cloned_atom[key] = undefined
345 | if (key === "parent" && cloned_atom.onPromote !== undefined) cloned_atom.onPromote(cloned_atom)
346 | continue
347 | }
348 | }
349 | if (key === "portals") {
350 | cloned_atom[key] = {}
351 | for (const subKey in atom[key]) {
352 | if (atoms.includes(atom[key][subKey])) {
353 | cloned_atom[key][subKey] = cloned_atoms[atom[key][subKey].atom_id]
354 | }
355 | else {
356 | cloned_atom[key][subKey] = atom[key][subKey]
357 | }
358 | }
359 | continue
360 | }
361 | if (key === "links") {
362 | cloned_atom.links = []
363 | for (const link of atom.links) {
364 | const cloned_link = {}
365 | cloned_link.offset = link.offset
366 | cloned_link.transfer = link.transfer
367 |
368 | if (atoms[link.atom.atom_id] === link.atom) {
369 | cloned_link.atom = cloned_atoms[link.atom.atom_id]
370 | //cloned_link.atom = link.atom
371 | cloned_atom.links.push(cloned_link)
372 | }
373 | else {
374 | cloned_link.atom = link.atom
375 | const a = cloned_link.atom
376 | if (a.world.atoms.includes(a)) {
377 | cloned_atom.links.push(cloned_link)
378 | }
379 | }
380 |
381 |
382 | }
383 | continue
384 | }
385 |
386 | cloned_atom[key] = atom[key]
387 |
388 | }
389 | }
390 |
391 |
392 |
393 | return cloned_atoms
394 | }
395 |
396 | const cloneWorld = (world) => {
397 | const cloned_world = makeWorld()
398 | const cloned_atoms = betterCloneAtoms(world.atoms, cloned_world)
399 | cloned_world.atoms = cloned_atoms
400 | cloned_world.id = world.id
401 | return cloned_world
402 | }
403 |
404 | //===========//
405 | // Game Loop //
406 | //===========//
407 | const prepWorld = (world) => {
408 | for (const atom of world.atoms) {
409 | atom.dx = atom.nextdx
410 | atom.dy = atom.nextdy
411 | }
412 | }
413 |
414 | const savePastProjection = (world) => {
415 | const projection = cloneWorld(world)
416 | projection.isProjection = true
417 | world.pastProjections.unshift(projection)
418 | //world.pastProjections.length = 60
419 | }
420 |
421 | const saveFutureProjection = (world, num=30, autoCatchUp=true) => {
422 | //world.futureProjections = []
423 | const projection = cloneWorld(world)
424 | projection.isProjection = true
425 | world.futureProjection = projection
426 | projection.realWorld = world
427 | if (world.futureProjLength !== undefined) {
428 | num = world.futureProjLength
429 | }
430 | for (let i = 0; i < num; i++) {
431 | //const p = cloneWorld(world.futureProjection)
432 | //p.isProjection = true
433 | const p = world.futureProjection
434 | p.isOnCatchup = i < num-1
435 | if (!autoCatchUp) p.isOnCatchup = true
436 | updateWorld(p)
437 | prepWorld(p)
438 | //world.futureProjection = p
439 | p.isOnCatchup = false
440 | }
441 | projection.isOnCatchup = false
442 | }
443 |
444 | const fullUpdateWorld = (world) => {
445 | updateWorld(world)
446 | prepWorld(world)
447 |
448 | }
449 |
450 | const killOrphans = (world) => {
451 | for (const atom of world.atoms) {
452 | if (atom.parent === undefined) continue
453 | if (!atom.parent.links.some(link => link.atom === atom)) {
454 | removeAtom(world, atom)
455 | print("ORPHAN")
456 | }
457 | }
458 | }
459 |
460 | const updateWorld = (world) => {
461 |
462 | for (const atom of world.atoms) {
463 | if (atom.futureProjLength !== undefined) {
464 | world.futureProjLength = atom.futureProjLength
465 | break
466 | }
467 | }
468 |
469 | if (world.overridePaused) return
470 |
471 | if (world.rewindAutoPlay !== undefined) {
472 |
473 | if (world.bonusAtoms !== undefined) {
474 | for (const ba of world.bonusAtoms) {
475 | for (const a of world.atoms) {
476 | if (a === ba) continue
477 | if (a.update === UPDATE_MOVER_BEING) {
478 |
479 | if (atomOverlaps(ba, a)) {
480 | world.rewindPrev = undefined
481 | world.rewindAutoPlay = undefined
482 | }
483 | }
484 | }
485 | }
486 | }
487 |
488 | //if (world.rewindI === undefined) world.rewindI = 0
489 | const next = world.rewindAutoPlay.shift()
490 | if (next !== undefined) {
491 | world.atoms = next.atoms
492 | world.rewindPrev = next
493 | if (world.bonusAtoms !== undefined) {
494 | world.atoms.push(...world.bonusAtoms)
495 | for (const a of world.bonusAtoms) {
496 | a.nextdx = a.dx
497 | a.nextdy = a.dy
498 | updateAtom(a, world)
499 | }
500 | }
501 | return
502 | }
503 | world.rewindAutoPlay = undefined
504 | savePastProjection(world)
505 |
506 | const id = multiverse.worlds.indexOf(world)
507 | multiverse.worlds[id] = cloneWorld(world.rewindPrev)
508 | }
509 |
510 | if (world.isSlow) {
511 | if (world.slowTick === undefined) {
512 | world.slowTick = true
513 | }
514 | if (world.slowTick) {
515 | world.slowTick = !world.slowTick
516 | return
517 | }
518 | world.slowTick = !world.slowTick
519 | }
520 |
521 | if (world.pruneTimer !== undefined) {
522 | if (world.pruneTimer <= 0) {
523 | return removeWorld(multiverse, world)
524 | }
525 | else {
526 | world.pruneTimer--
527 | }
528 |
529 | }
530 | if (world.bounceTimer !== undefined) {
531 | world.bounceTimer--
532 | if (world.bounceTimer < 0) {
533 | world.bounceTimer = undefined
534 | saveFutureProjection(world)
535 | }
536 | }
537 |
538 | let isFreeze = false
539 | const freezeExceptions = []
540 |
541 |
542 | if (world.fadeReliance !== undefined) {
543 | if (world.fadeReliance > 0) {
544 | world.fadeReliance--
545 | }
546 | else for (const atom of world.atoms) {
547 |
548 | if (atom.isFreezeFadeType) {
549 | isFreeze = true
550 | freezeExceptions.push(atom.fadeReliantOn)
551 | continue
552 | }
553 |
554 | else if (atom.isMadFadeType) {
555 | print("MAD")
556 | world.fadeReliance = undefined
557 | removeAtom(world, atom.fadeReliantOn, {destroy: false})
558 | const mad = makeAtom({...ELEMENT_FROG_CYAN_MAD, x: atom.fadeReliantOn.x, y: atom.fadeReliantOn.y, flipX: atom.fadeReliantOn.flipX})
559 | addAtom(world, mad)
560 | atom.fadeReliantOn = undefined
561 | continue
562 | }
563 |
564 | else if (atom.isNexusFadeType) {
565 | //atom.fadeReliantOn.world.pruneTimer.d
566 | //const reworld = cloneWorld(world)
567 | //const id = multiverse.worlds.indexOf(world)
568 | //multiverse.worlds[id] = reworld
569 | addWorld(multiverse, cloneWorld(atom.nexusWorldStart))
570 | continue
571 | }
572 |
573 | if (atom.opacity === undefined) atom.opacity = 1
574 | //print(atom.fadeReliantOn)
575 | if (world.atoms.includes(atom.fadeReliantOn)) {
576 | atom.opacity -= 0.02
577 | if (atom.opacity <= 0) {
578 | removeAtom(world, atom)
579 | }
580 | }
581 | else {
582 | atom.opacity = 1.0
583 | }
584 | }
585 | }
586 |
587 | if (isFreeze) {
588 | for (const atom of freezeExceptions) {
589 | if (atom !== undefined) {
590 | atom.nextdx = atom.dx
591 | atom.nextdy = atom.dy
592 | }
593 | }
594 |
595 | for (const atom of freezeExceptions) {
596 | if (atom !== undefined) updateAtom(atom, world)
597 | }
598 | return
599 | }
600 |
601 | if (world.futureNowRecordings !== undefined) {
602 |
603 | if (world.futureNowReplay !== undefined) {
604 |
605 | if (world.futureNowRecordings[world.futureNowReplay] === undefined) {
606 | removeWorld(multiverse, world)
607 | world.futureNowBaseWorld.overridePaused = false
608 | return
609 | }
610 |
611 | replaceWorld(world.futureNowBaseWorld, world.futureNowRecordings[world.futureNowReplay])
612 | world.futureNowRecordings[world.futureNowReplay].overridePaused = true
613 | world.futureNowBaseWorld = world.futureNowRecordings[world.futureNowReplay]
614 | world.futureNowReplay++
615 | }
616 | else {
617 | const copy = cloneWorld(world)
618 | world.futureNowRecordings.push(copy)
619 |
620 | if (world.futureNowRecordings.length >= 29) {
621 |
622 | //removeWorld(multiverse, world.futureNowBaseWorld)
623 | //world.futureNowRecordings = undefined
624 | world.futureNowReplay = 0
625 |
626 |
627 | }
628 | }
629 | }
630 |
631 | if (!world.isProjection) {
632 | if (world.projection_skip > 0) {
633 | world.projection_skip--
634 | }
635 | else {
636 | savePastProjection(world)
637 | }
638 | //print("update real", world.id)
639 |
640 | }
641 |
642 | if (!world.isProjection && world.atoms.some(a => a.requiresFutureProjections)) {
643 | if (world.futureProjection === undefined || !world.futureProjection.isProjection) {
644 | if (world.future_projection_skip > 0) {
645 | world.future_projection_skip--
646 | //saveFutureProjection(world, 30, false)
647 | //print("skip", world)
648 | }
649 | else {
650 |
651 | saveFutureProjection(world)
652 | }
653 | }
654 | else {
655 | /*if (world.future_projection_skip > 0) {
656 | world.future_projection_skip--
657 | }
658 | else {
659 | saveFutureProjection(world)
660 | }*/
661 | world.futureProjection.isOnCatchup = false
662 | fullUpdateWorld(world.futureProjection)
663 | //print("update", world.id)
664 | }
665 | }
666 |
667 | for (const atom of world.atoms) {
668 | atom.nextdx = atom.dx
669 | atom.nextdy = atom.dy
670 | }
671 |
672 | for (const atom of world.atoms) {
673 | updateAtom(atom, world)
674 | }
675 |
676 | killOrphans(world)
677 |
678 |
679 | if (world.atoms.some(a => a.requiresRefrogTracking)) {
680 | for (const atom of world.atoms) {
681 | if (!(atom.refrogTrack instanceof Array) && atom.refrogTrack instanceof Object) {
682 | atom.refrogTrack = Array.from(atom.refrogTrack)
683 | }
684 | if (atom.refrogTrackPlay === true) continue
685 | if (atom.refrogTrack === undefined) {
686 | atom.refrogTrack = []
687 | }
688 | atom.refrogTrack.push({x: atom.x, y: atom.y})
689 | }
690 | }
691 | }
692 |
693 | const updateWorldLinks = (world) => {
694 | for (const atom of world.atoms) {
695 | updateAtomLinks(atom)
696 | }
697 | }
698 |
699 | const drawWorld = (world, context, colourBackground = true) => {
700 |
701 | if (world.isHidden) return
702 |
703 |
704 | if (colourBackground) {
705 | context.fillStyle = INVERT? Colour.Silver : Colour.Grey
706 | context.fillRect(0, 0, WORLD_WIDTH, WORLD_HEIGHT)
707 | }
708 |
709 | //if (world.pastProjections === undefined) return
710 | //const projection = world.pastProjections[30]
711 | const projection = world
712 | if (projection !== undefined) {
713 | for (const atom of projection.atoms) {
714 | drawAtom(atom, context)
715 | }
716 | }
717 |
718 | }
719 |
--------------------------------------------------------------------------------