pixospritz-core 0.10.1 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +36 -286
  2. package/dist/bundle.js +13 -3
  3. package/dist/bundle.js.map +1 -1
  4. package/dist/style.css +1 -0
  5. package/package.json +43 -44
  6. package/src/components/WebGLView.jsx +318 -0
  7. package/src/css/pixos.css +372 -0
  8. package/src/engine/actions/animate.js +41 -0
  9. package/src/engine/actions/changezone.js +135 -0
  10. package/src/engine/actions/chat.js +109 -0
  11. package/src/engine/actions/dialogue.js +90 -0
  12. package/src/engine/actions/face.js +22 -0
  13. package/src/engine/actions/greeting.js +28 -0
  14. package/src/engine/actions/interact.js +86 -0
  15. package/src/engine/actions/move.js +67 -0
  16. package/src/engine/actions/patrol.js +109 -0
  17. package/src/engine/actions/prompt.js +185 -0
  18. package/src/engine/actions/script.js +42 -0
  19. package/src/engine/core/audio/AudioSystem.js +543 -0
  20. package/src/engine/core/cutscene/PxcPlayer.js +956 -0
  21. package/src/engine/core/cutscene/manager.js +243 -0
  22. package/src/engine/core/database/index.js +75 -0
  23. package/src/engine/core/debug/index.js +371 -0
  24. package/src/engine/core/hud/index.js +765 -0
  25. package/src/engine/core/index.js +540 -0
  26. package/src/engine/core/input/gamepad/Controller.js +71 -0
  27. package/src/engine/core/input/gamepad/ControllerButtons.js +231 -0
  28. package/src/engine/core/input/gamepad/ControllerStick.js +173 -0
  29. package/src/engine/core/input/gamepad/index.js +592 -0
  30. package/src/engine/core/input/keyboard.js +196 -0
  31. package/src/engine/core/input/manager.js +485 -0
  32. package/src/engine/core/input/mouse.js +203 -0
  33. package/src/engine/core/input/touch.js +175 -0
  34. package/src/engine/core/mode/manager.js +199 -0
  35. package/src/engine/core/net/manager.js +535 -0
  36. package/src/engine/core/queue/action.js +83 -0
  37. package/src/engine/core/queue/event.js +82 -0
  38. package/src/engine/core/queue/index.js +44 -0
  39. package/src/engine/core/queue/loadable.js +33 -0
  40. package/src/engine/core/render/CameraEffects.js +494 -0
  41. package/src/engine/core/render/FrustumCuller.js +417 -0
  42. package/src/engine/core/render/LODManager.js +285 -0
  43. package/src/engine/core/render/ParticleManager.js +529 -0
  44. package/src/engine/core/render/TextureAtlas.js +465 -0
  45. package/src/engine/core/render/camera.js +338 -0
  46. package/src/engine/core/render/light.js +197 -0
  47. package/src/engine/core/render/manager.js +1079 -0
  48. package/src/engine/core/render/shaders.js +110 -0
  49. package/src/engine/core/render/skybox.js +342 -0
  50. package/src/engine/core/resource/manager.js +133 -0
  51. package/src/engine/core/resource/object.js +611 -0
  52. package/src/engine/core/resource/texture.js +103 -0
  53. package/src/engine/core/resource/tileset.js +177 -0
  54. package/src/engine/core/scene/avatar.js +215 -0
  55. package/src/engine/core/scene/speech.js +138 -0
  56. package/src/engine/core/scene/sprite.js +702 -0
  57. package/src/engine/core/scene/spritz.js +189 -0
  58. package/src/engine/core/scene/world.js +681 -0
  59. package/src/engine/core/scene/zone.js +1167 -0
  60. package/src/engine/core/store/index.js +110 -0
  61. package/src/engine/dynamic/animatedSprite.js +64 -0
  62. package/src/engine/dynamic/animatedTile.js +98 -0
  63. package/src/engine/dynamic/avatar.js +110 -0
  64. package/src/engine/dynamic/map.js +174 -0
  65. package/src/engine/dynamic/sprite.js +255 -0
  66. package/src/engine/dynamic/spritz.js +119 -0
  67. package/src/engine/events/EventSystem.js +609 -0
  68. package/src/engine/events/camera.js +142 -0
  69. package/src/engine/events/chat.js +75 -0
  70. package/src/engine/events/menu.js +186 -0
  71. package/src/engine/scripting/CallbackManager.js +514 -0
  72. package/src/engine/scripting/PixoScriptInterpreter.js +81 -0
  73. package/src/engine/scripting/PixoScriptLibrary.js +704 -0
  74. package/src/engine/shaders/effects/index.js +450 -0
  75. package/src/engine/shaders/fs.js +222 -0
  76. package/src/engine/shaders/particles/fs.js +41 -0
  77. package/src/engine/shaders/particles/vs.js +61 -0
  78. package/src/engine/shaders/picker/fs.js +34 -0
  79. package/src/engine/shaders/picker/init.js +62 -0
  80. package/src/engine/shaders/picker/vs.js +42 -0
  81. package/src/engine/shaders/pxsl/README.md +250 -0
  82. package/src/engine/shaders/pxsl/index.js +25 -0
  83. package/src/engine/shaders/pxsl/library.js +608 -0
  84. package/src/engine/shaders/pxsl/manager.js +338 -0
  85. package/src/engine/shaders/pxsl/specification.js +363 -0
  86. package/src/engine/shaders/pxsl/transpiler.js +753 -0
  87. package/src/engine/shaders/skybox/cosmic/fs.js +147 -0
  88. package/src/engine/shaders/skybox/cosmic/vs.js +23 -0
  89. package/src/engine/shaders/skybox/matrix/fs.js +127 -0
  90. package/src/engine/shaders/skybox/matrix/vs.js +23 -0
  91. package/src/engine/shaders/skybox/morning/fs.js +109 -0
  92. package/src/engine/shaders/skybox/morning/vs.js +23 -0
  93. package/src/engine/shaders/skybox/neon/fs.js +119 -0
  94. package/src/engine/shaders/skybox/neon/vs.js +23 -0
  95. package/src/engine/shaders/skybox/sky/fs.js +114 -0
  96. package/src/engine/shaders/skybox/sky/vs.js +23 -0
  97. package/src/engine/shaders/skybox/sunset/fs.js +101 -0
  98. package/src/engine/shaders/skybox/sunset/vs.js +23 -0
  99. package/src/engine/shaders/transition/blur/fs.js +42 -0
  100. package/src/engine/shaders/transition/blur/vs.js +26 -0
  101. package/src/engine/shaders/transition/cross/fs.js +36 -0
  102. package/src/engine/shaders/transition/cross/vs.js +26 -0
  103. package/src/engine/shaders/transition/crossBlur/fs.js +41 -0
  104. package/src/engine/shaders/transition/crossBlur/vs.js +25 -0
  105. package/src/engine/shaders/transition/dissolve/fs.js +78 -0
  106. package/src/engine/shaders/transition/dissolve/vs.js +24 -0
  107. package/src/engine/shaders/transition/fade/fs.js +31 -0
  108. package/src/engine/shaders/transition/fade/vs.js +27 -0
  109. package/src/engine/shaders/transition/iris/fs.js +52 -0
  110. package/src/engine/shaders/transition/iris/vs.js +24 -0
  111. package/src/engine/shaders/transition/pixelate/fs.js +44 -0
  112. package/src/engine/shaders/transition/pixelate/vs.js +24 -0
  113. package/src/engine/shaders/transition/slide/fs.js +53 -0
  114. package/src/engine/shaders/transition/slide/vs.js +24 -0
  115. package/src/engine/shaders/transition/swirl/fs.js +39 -0
  116. package/src/engine/shaders/transition/swirl/vs.js +26 -0
  117. package/src/engine/shaders/transition/wipe/fs.js +50 -0
  118. package/src/engine/shaders/transition/wipe/vs.js +24 -0
  119. package/src/engine/shaders/vs.js +60 -0
  120. package/src/engine/utils/CameraController.js +506 -0
  121. package/src/engine/utils/ObjHelper.js +551 -0
  122. package/src/engine/utils/debug-logger.js +110 -0
  123. package/src/engine/utils/enums.js +305 -0
  124. package/src/engine/utils/generator.js +156 -0
  125. package/src/engine/utils/index.js +21 -0
  126. package/src/engine/utils/loaders/ActionLoader.js +77 -0
  127. package/src/engine/utils/loaders/AudioLoader.js +157 -0
  128. package/src/engine/utils/loaders/EventLoader.js +66 -0
  129. package/src/engine/utils/loaders/ObjectLoader.js +67 -0
  130. package/src/engine/utils/loaders/SpriteLoader.js +77 -0
  131. package/src/engine/utils/loaders/TilesetLoader.js +103 -0
  132. package/src/engine/utils/loaders/index.js +21 -0
  133. package/src/engine/utils/math/matrix4.js +367 -0
  134. package/src/engine/utils/math/vector.js +458 -0
  135. package/src/engine/utils/obj/_old_js/index.js +46 -0
  136. package/src/engine/utils/obj/_old_js/layout.js +308 -0
  137. package/src/engine/utils/obj/_old_js/material.js +711 -0
  138. package/src/engine/utils/obj/_old_js/mesh.js +761 -0
  139. package/src/engine/utils/obj/_old_js/utils.js +647 -0
  140. package/src/engine/utils/obj/index.js +24 -0
  141. package/src/engine/utils/obj/js/index.js +277 -0
  142. package/src/engine/utils/obj/js/loader.js +232 -0
  143. package/src/engine/utils/obj/layout.js +246 -0
  144. package/src/engine/utils/obj/material.js +665 -0
  145. package/src/engine/utils/obj/mesh.js +657 -0
  146. package/src/engine/utils/obj/ts/index.ts +72 -0
  147. package/src/engine/utils/obj/ts/layout.ts +265 -0
  148. package/src/engine/utils/obj/ts/material.ts +760 -0
  149. package/src/engine/utils/obj/ts/mesh.ts +785 -0
  150. package/src/engine/utils/obj/ts/utils.ts +501 -0
  151. package/src/engine/utils/obj/utils.js +428 -0
  152. package/src/engine/utils/resources.js +18 -0
  153. package/src/index.jsx +55 -0
  154. package/src/spritz/player.js +18 -0
  155. package/src/spritz/readme.md +18 -0
  156. package/LICENSE +0 -437
  157. package/dist/bundle.js.LICENSE.txt +0 -31
@@ -0,0 +1,90 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ export default {
15
+ // Initialize Dialogue Object
16
+ init: function (text, scrolling = true, options = {}) {
17
+ this.engine = this.sprite.engine;
18
+ this.text = text; // holds queue of dialogue
19
+ this.displayText = typeof text === 'string' ? text : this.text.shift(); // current statement
20
+ this.scrolling = scrolling;
21
+ this.options = options;
22
+ this.completed = false;
23
+ this.lastText = false;
24
+ this.speechOutput = true;
25
+ this.lastKey = new Date().getTime();
26
+ this.loaded = true;
27
+ },
28
+ // Update & Scroll
29
+ tick: function (time) {
30
+ if (!this.loaded) return;
31
+ // Check for Dialogue Completion (TODO - manual triggers + scroll / sections)
32
+ if (this.options && this.options.autoclose) {
33
+ this.endTime = this.endTime ? this.endTime : this.options.endTime ?? new Date().getTime() + (this.options.duration * 1000 ?? 10000); // 10 seconds default if autoclose
34
+ if (time > this.endTime) {
35
+ this.completed = true;
36
+ }
37
+ }
38
+ // Handle Input
39
+ this.checkInput(time);
40
+
41
+ // Dialogue
42
+ this.sprite.speak(this.displayText, false, this);
43
+
44
+ // Callback on Completion
45
+ if (this.completed && this.options.onClose) {
46
+ if (this.sprite.speech.clearHud) {
47
+ this.sprite.speech.clearHud();
48
+ }
49
+ this.options.onClose();
50
+ }
51
+ return this.completed;
52
+ },
53
+ // Handle Keyboard
54
+ checkInput: function (time) {
55
+ if (time > this.lastKey + 100) {
56
+ switch (this.engine.keyboard.lastPressedCode()) {
57
+ case 'Escape':
58
+ this.sprite.speak(false);
59
+ this.completed = true; // toggle
60
+ break;
61
+ case 'Enter':
62
+ if (typeof this.text === 'string' || this.text.length === 0) {
63
+ this.sprite.speak(false);
64
+ this.completed = true;
65
+ } else {
66
+ this.completed = false;
67
+ this.displayText = this.text.shift();
68
+ window.speechSynthesis.cancel();
69
+ this.speechOutput = true;
70
+ this.sprite.speak(this.displayText, false, this);
71
+ }
72
+ break;
73
+ }
74
+ // gamepad
75
+ if (this.engine.gamepad.keyPressed('a')) {
76
+ if (typeof this.text === 'string' || this.text.length === 0) {
77
+ this.sprite.speak(false);
78
+ this.completed = true;
79
+ } else {
80
+ this.completed = false;
81
+ this.displayText = this.text.shift();
82
+ window.speechSynthesis.cancel();
83
+ this.speechOutput = true;
84
+ this.sprite.speak(this.displayText, false, this);
85
+ }
86
+ return;
87
+ }
88
+ }
89
+ },
90
+ };
@@ -0,0 +1,22 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ export default {
15
+ init: function (facing) {
16
+ this.facing = facing;
17
+ },
18
+ tick: function (time) {
19
+ if (this.facing && this.facing != this.sprite.facing) this.sprite.setFacing(this.facing);
20
+ return true;
21
+ },
22
+ };
@@ -0,0 +1,28 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ export default {
15
+ // Initialize Dialogue Object
16
+ init: function (greeting, options = {}) {
17
+ this.engine = this.sprite.engine;
18
+ this.greeting = greeting;
19
+ this.options = options;
20
+ this.completed = false;
21
+ },
22
+ // Update & Scroll
23
+ tick: function (time) {
24
+ if (!this.loaded) return;
25
+ this.sprite.setGreeting(this.text);
26
+ return true;
27
+ },
28
+ };
@@ -0,0 +1,86 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import { Vector } from '@Engine/utils/math/vector.js';
15
+ import { Direction } from '@Engine/utils/enums.js';
16
+
17
+ export default {
18
+ init: async function (from, facing, world) {
19
+ this.world = world;
20
+ this.from = new Vector(...from);
21
+ this.facing = facing;
22
+ this.offset = Direction.toOffset(facing);
23
+ this.lastKey = new Date().getTime();
24
+ this.completed = false;
25
+ // Determine Tile
26
+ this.to = [from[0] + this.offset[0], from[1] + this.offset[1]];
27
+ // Check for Sprites at that point
28
+ this.zone = world.zoneContaining(...this.to);
29
+ // Trigger interaction on Sprite
30
+ this.spriteList = this.zone.spriteList.filter((sprite) => sprite.pos.x === this.to[0] && sprite.pos.y === this.to[1]);
31
+ this.objectList = this.zone.objectList.filter((object) => object.pos.x === this.to[0] && object.pos.y === this.to[1]);
32
+ // -- pass through reference to "finish()" callback
33
+ this.finish = this.finish.bind(this);
34
+ // Trigger
35
+ this.interact();
36
+ },
37
+ // Trigger interactions in sprites
38
+ interact: async function () {
39
+ if (this.spriteList.length === 0 && this.objectList.length === 0) this.completed = true;
40
+ // objects
41
+ await Promise.all(this.objectList.map(async (object) => {
42
+ let faceChange = object.faceDir(Direction.reverse(this.facing));
43
+ if (faceChange) {
44
+ object.addAction(faceChange); // face towards avatar
45
+ }
46
+ return object.interact ? await this.zone.objectDict[object.id].interact(this.sprite, this.finish) : null;
47
+ }));
48
+ // sprite
49
+ await Promise.all(this.spriteList.map(async (sprite) => {
50
+ let faceChange = sprite.faceDir(Direction.reverse(this.facing));
51
+ if (faceChange) {
52
+ sprite.addAction(faceChange); // face towards avatar
53
+ }
54
+ return sprite.interact ? await this.zone.spriteDict[sprite.id].interact(this.sprite, this.finish) : null;
55
+ }));
56
+ },
57
+ // Callback to clear interaction
58
+ finish: function (result) {
59
+ if (result) this.completed = true;
60
+ },
61
+ // check input and completion
62
+ tick: function (time) {
63
+ if (!this.loaded) return;
64
+ this.checkInput(time);
65
+ return this.completed; // loop
66
+ },
67
+ // Handle Keyboard
68
+ checkInput: function (time) {
69
+ if (time > this.lastKey + Math.max(this.length, 200)) {
70
+ switch (this.sprite.engine.keyboard.lastPressed('q')) {
71
+ // close dialogue on q key press
72
+ case 'q':
73
+ // Needs to Cancel the Interaction on the Affected Sprite as well
74
+ this.completed = true; // toggle
75
+ break;
76
+ default:
77
+ this.lastKey = new Date().getTime();
78
+ return null;
79
+ }
80
+ }
81
+ // gamepad
82
+ if (this.sprite.engine.gamepad.keyPressed('a')) {
83
+ this.completed = true;
84
+ }
85
+ },
86
+ };
@@ -0,0 +1,67 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import { Vector, set, lerp } from '@Engine/utils/math/vector.js';
15
+ import { Direction } from '@Engine/utils/enums.js';
16
+
17
+ export default {
18
+ init: function (from, to, length, zone) {
19
+ this.zone = zone;
20
+ this.from = new Vector(...from);
21
+ this.to = new Vector(...to);
22
+ this.facing = Direction.fromOffset([Math.round(to.x - from.x), Math.round(to.y - from.y)]);
23
+ this.length = length;
24
+ this.spriteList = this.zone.spriteList.filter((sprite) => sprite.pos.x === this.to.x && sprite.pos.y === this.to.y);
25
+ },
26
+ // move
27
+ tick: function (time) {
28
+ if (!this.loaded) return;
29
+ // Set facing
30
+ if (this.facing && this.facing != this.sprite.facing) this.sprite.setFacing(this.facing);
31
+ // Transition & Move
32
+ let endTime = this.startTime + this.length;
33
+ let frac = (time - this.startTime) / this.length;
34
+ if (time >= endTime) {
35
+ this.sprite.pos.x = this.to.x;
36
+ this.sprite.pos.y = this.to.y;
37
+ let hx = this.sprite.pos.x + this.sprite.hotspotOffset.x;
38
+ let hy = this.sprite.pos.y + this.sprite.hotspotOffset.y;
39
+ this.sprite.pos.z = this.sprite.zone.getHeight(hx, hy);
40
+ frac = 1;
41
+ this.onStep();
42
+ // Get next frame
43
+ let newFrame = Math.floor(frac * 4);
44
+ if (newFrame != this.sprite.animFrame) this.sprite.setFrame(newFrame);
45
+ return time >= endTime;
46
+ } else {
47
+ this.sprite.pos.x = this.from.x + frac * (this.to.x - this.from.x);
48
+ this.sprite.pos.y = this.from.y + frac * (this.to.y - this.from.y);
49
+ let hx = this.sprite.pos.x + this.sprite.hotspotOffset.x;
50
+ let hy = this.sprite.pos.y + this.sprite.hotspotOffset.y;
51
+ this.sprite.pos.z = this.sprite.zone.getHeight(hx, hy);
52
+ // Get next frame
53
+ let newFrame = Math.floor(frac * 4);
54
+ if (newFrame != this.sprite.animFrame) this.sprite.setFrame(newFrame);
55
+ }
56
+ return false;
57
+ },
58
+ // Trigger interactions in sprite when finished moving
59
+ onStep: async function () {
60
+ if (this.spriteList.length === 0) this.completed = true;
61
+ await Promise.all(
62
+ this.spriteList.map(async (sprite) => {
63
+ return sprite.onStep ? await sprite.onStep(sprite, sprite) : null;
64
+ })
65
+ );
66
+ },
67
+ };
@@ -0,0 +1,109 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import { Vector } from '@Engine/utils/math/vector.js';
15
+ import { Direction } from '@Engine/utils/enums.js';
16
+ import { ActionLoader } from '@Engine/utils/loaders/index.js';
17
+
18
+ export default {
19
+ init: async function (from, to, moveLength, zone) {
20
+ this.zone = zone;
21
+ this.from = new Vector(...from);
22
+ this.to = new Vector(...to);
23
+ this.lastKey = new Date().getTime();
24
+ this.completed = false;
25
+ this.direction = 1;
26
+ this.audio = await this.zone.engine.resourceManager.audioLoader.loadFromZip(this.sprite.zip, this.sprite.patrolSound ?? 'sewer-beat.mp3', true);
27
+ // Determine Path to Walk
28
+ [this.hasMoves, this.moveList] = this.sprite.zone.world.pathFind(from, to);
29
+ if (!this.hasMoves) {
30
+ this.completed = true; // no path - do not patrol
31
+ }
32
+ this.moveIndex = 1; // holds index position
33
+ this.moveLength = moveLength; // length of time per move
34
+
35
+ if (this.zone.audio) this.zone.audio.pauseAudio();
36
+ if (this.audio) this.audio.playAudio();
37
+ },
38
+ tick: function (time) {
39
+ if (!this.loaded) return;
40
+ this.checkInput(time);
41
+ // load up moves - todo (improve this and make it less manual)
42
+ let endTime = this.startTime + this.moveLength;
43
+ if (time > endTime) {
44
+ let move = this.moveList[this.moveIndex];
45
+ if (this.moveList.length > 2) {
46
+ // last position (for facing)
47
+ let last =
48
+ this.moveIndex == 0
49
+ ? this.moveList[this.moveIndex + 1]
50
+ : this.moveIndex + 1 >= this.moveList.length
51
+ ? this.moveList[this.moveList.length - 1]
52
+ : this.moveList[this.moveIndex - 1];
53
+ let facing = Direction.fromOffset([Math.round(move[0] - last[0]), Math.round(move[1] - last[1])]);
54
+ // Check for zone change
55
+ if (!this.sprite.zone.isInZone(move[0], move[1])) {
56
+ let zone = this.sprite.zone.world.zoneContaining(move[0], move[1]);
57
+ if (!zone || !zone.loaded || !zone.isWalkable(move[0], move[1], Direction.reverse(facing))) {
58
+ this.currentAction = this.sprite.faceDir(facing);
59
+ } else {
60
+ this.currentAction = new ActionLoader(
61
+ this.sprite.engine,
62
+ 'changezone',
63
+ [this.sprite.zone.id, this.sprite.pos.toArray(), zone.id, move, this.moveLength],
64
+ this.sprite
65
+ );
66
+ }
67
+ } else {
68
+ // Load Next move
69
+ this.currentAction = new ActionLoader(this.sprite.engine, 'move', [last, move, this.moveLength, this.zone], this.sprite);
70
+ }
71
+
72
+ if (this.sprite.facing !== facing) {
73
+ this.currentAction = this.sprite.faceDir(facing);
74
+ // return this.completed;
75
+ }
76
+
77
+ if (this.currentAction) {
78
+ this.currentAction.facing = facing;
79
+ this.sprite.addAction(Promise.resolve(this.currentAction)).then(() => {});
80
+ }
81
+ }
82
+ // stop when done
83
+ if (this.moveIndex + this.direction >= this.moveList.length) {
84
+ this.direction *= -1;
85
+ this.completed = true;
86
+ if (this.zone.audio) this.zone.audio.playAudio();
87
+ if (this.audio) this.audio.pauseAudio();
88
+ }
89
+ this.moveIndex += this.direction;
90
+ this.startTime = time;
91
+ }
92
+ return this.completed; // loop
93
+ },
94
+ // Handle Keyboard
95
+ checkInput: function (time) {
96
+ if (time > this.lastKey + Math.max(this.moveLength, 200)) {
97
+ switch (this.sprite.engine.keyboard.lastPressed('q')) {
98
+ // quit on q key press
99
+ case 'q':
100
+ if (this.zone.audio) this.zone.audio.playAudio();
101
+ if (this.audio) this.audio.pauseAudio();
102
+ this.completed = true; // toggle
103
+ default:
104
+ this.lastKey = new Date().getTime();
105
+ return null;
106
+ }
107
+ }
108
+ },
109
+ };
@@ -0,0 +1,185 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ export default {
15
+ // Initialize Dialogue Object
16
+ init: function (menu, activeMenus, scrolling = true, options = {}) {
17
+ this.engine = this.sprite.engine;
18
+ this.text = '';
19
+ this.scrolling = scrolling;
20
+ this.line = 0;
21
+ this.options = options;
22
+ this.completed = false;
23
+ this.lastKey = new Date().getTime();
24
+ this.listenerId = this.engine.gamepad.attachListener(this.hookListener());
25
+ this.touches = [];
26
+ this.menuDict = menu ?? {};
27
+ this.activeMenus = activeMenus ?? [];
28
+ this.selectedMenu = {};
29
+ this.selectedMenuId = null;
30
+ this.isTouched = false;
31
+ },
32
+ // Update & Scroll
33
+ tick: function (time) {
34
+ if (!this.loaded) return;
35
+ // Check for Dialogue Completion (TODO - manual triggers + scroll / sections)
36
+ if (this.options && this.options.autoclose) {
37
+ this.endTime = this.endTime ? this.endTime : this.options.endTime ?? new Date().getTime() + 10000; // 10 seconds default if autoclose
38
+ if (time > this.endTime) {
39
+ this.completed = true;
40
+ }
41
+ }
42
+
43
+ // Handle Input
44
+ this.checkInput(time);
45
+ // Draw Active Menus to Screen
46
+ Object.keys(this.menuDict)
47
+ .filter((key) => this.activeMenus.includes(key))
48
+ .map((id) => {
49
+ let section = this.menuDict[id];
50
+ let colors = section.colours;
51
+ if (section.active) {
52
+ colors['background'] = '#555';
53
+ }
54
+ this.engine.hud.drawButton(section.text, section.x, section.y, section.w, section.h, section.colours);
55
+ });
56
+
57
+ this.textbox = this.engine.hud.scrollText(this.prompt + this.text, this.scrolling, this.options);
58
+
59
+ if (this.completed) {
60
+ this.unhookListener();
61
+ // this.engine.hud.clearHud();
62
+ }
63
+ return this.completed;
64
+ },
65
+
66
+ unhookListener: function () {
67
+ // remove listener
68
+ this.engine.gamepad.removeListener(this.listenerId);
69
+ this.listenerId = null;
70
+ },
71
+
72
+ hookListener: function () {
73
+ /**
74
+ * Normalize event to extract touches array.
75
+ * Works with mouse events, touch events, and pre-processed events from WebGLView.
76
+ */
77
+ const normalizeTouches = (e) => {
78
+ // Pre-computed canvas coordinates from WebGLView
79
+ if (e.canvasX !== undefined && e.canvasY !== undefined) {
80
+ return [{ x: e.canvasX, y: e.canvasY, identifier: 'normalized' }];
81
+ }
82
+ // Touch events
83
+ if (e.touches && e.touches.length > 0) {
84
+ return Array.from(e.touches).map(t => ({ x: t.clientX, y: t.clientY, identifier: t.identifier }));
85
+ }
86
+ // Touch end uses changedTouches
87
+ if (e.changedTouches && e.changedTouches.length > 0) {
88
+ return Array.from(e.changedTouches).map(t => ({ x: t.clientX, y: t.clientY, identifier: t.identifier }));
89
+ }
90
+ // Mouse events
91
+ if (e.clientX !== undefined && e.clientY !== undefined) {
92
+ return [{ x: e.clientX, y: e.clientY, identifier: 'mouse' }];
93
+ }
94
+ // Already normalized (legacy format)
95
+ if (e.touches) {
96
+ return e.touches;
97
+ }
98
+ return [];
99
+ };
100
+
101
+ let touchstart = (e) => {
102
+ const touches = normalizeTouches(e);
103
+ this.isTouched = true;
104
+ this.touches = touches;
105
+ if (this.isTouched && this.touches.length > 0 && this.lastKey + 100 < new Date().getTime()) {
106
+ let x = this.touches[0].x;
107
+ let y = this.touches[0].y;
108
+ let self = this;
109
+ self.activeMenus
110
+ .filter((key) => {
111
+ let w = self.menuDict[key];
112
+ if (x < w.x + w.w && x > w.x && y < w.y + w.h && y > w.y) {
113
+ return true;
114
+ }
115
+ return false;
116
+ })
117
+ .map((key) => {
118
+ let w = self.menuDict[key];
119
+ if (w.trigger) w.trigger(this);
120
+ if (w.children) {
121
+ self.activeMenus = w.children;
122
+ }
123
+ });
124
+ }
125
+ };
126
+ let touchmove = (e) => {
127
+ this.touches = normalizeTouches(e);
128
+ };
129
+ let touchend = (e) => {
130
+ this.isTouched = false;
131
+ this.touches = normalizeTouches(e);
132
+ };
133
+ let mousemove = touchmove;
134
+ let mousedown = touchstart;
135
+ let mouseup = touchend;
136
+ return {
137
+ touchstart,
138
+ touchmove,
139
+ touchend,
140
+ mousedown,
141
+ mousemove,
142
+ mouseup,
143
+ };
144
+ },
145
+ // Handle Keyboard & Mouse & Touch
146
+ checkInput: function (time) {
147
+ // TODO -- Reimplement mouse / touch handler support
148
+
149
+ // TOOD -- Is there a cleaner way to do this? I feel like this should be less hacky
150
+ // Keyboard
151
+ if (time > this.lastKey + 200) {
152
+ let skipChar = false;
153
+ switch (this.engine.keyboard.lastPressedCode()) {
154
+ case 'Escape':
155
+ this.completed = true;
156
+ skipChar = true;
157
+ break;
158
+ case 'Backspace':
159
+ let arr = this.text.split('');
160
+ arr.pop();
161
+ this.text = arr.join('');
162
+ this.lastKey = time;
163
+ skipChar = true;
164
+ break;
165
+ case 'Enter':
166
+ this.sprite.setGreeting(this.text);
167
+ if (this.sprite.speech.clearHud) this.sprite.speech.clearHud();
168
+ this.speechbox = this.sprite.speech.scrollText(this.text);
169
+ this.sprite.speech.loadImage();
170
+ this.completed = true;
171
+ skipChar = true;
172
+ break;
173
+ }
174
+ // debounce keypresses
175
+ // write to chat box
176
+ if (!skipChar) {
177
+ let char = this.engine.keyboard.lastPressedKey();
178
+ if (char) {
179
+ this.lastKey = time;
180
+ this.text += '' + char;
181
+ }
182
+ }
183
+ }
184
+ },
185
+ };
@@ -0,0 +1,42 @@
1
+ /* *\
2
+ ** ----------------------------------------------- **
3
+ ** Calliope - Pixos Game Engine **
4
+ ** ----------------------------------------------- **
5
+ ** Copyright (c) 2020-2025 - Kyle Derby MacInnis **
6
+ ** **
7
+ ** Any unauthorized distribution or transfer **
8
+ ** of this work is strictly prohibited. **
9
+ ** **
10
+ ** All Rights Reserved. **
11
+ ** ----------------------------------------------- **
12
+ \* */
13
+
14
+ import { debug } from '@Engine/utils/debug-logger.js';
15
+
16
+ export default {
17
+ init: function (triggerId, zone, onCompleted = null) {
18
+ this.zone = zone;
19
+ this.world = zone.world;
20
+ this.lastKey = new Date().getTime();
21
+ this.completed = false;
22
+ this.onCompleted = () => debug('Script', 'script finished');
23
+ if (onCompleted) this.onCompleted = onCompleted;
24
+ // Determine Tile
25
+ this.triggerId = triggerId;
26
+ // Trigger
27
+ this.triggerScript();
28
+ },
29
+ // Trigger interactions in sprites
30
+ triggerScript: function () {
31
+ if (!this.triggerId) this.completed = true;
32
+ Promise.all(this.zone.scripts.filter((x) => x.id === this.triggerId).map(async (x) => await x.trigger.call(this.zone))).then(() => {
33
+ this.completed = true;
34
+ });
35
+ },
36
+ // check input and completion
37
+ tick: function (time) {
38
+ if (!this.loaded) return;
39
+ if (this.completed) this.onCompleted();
40
+ return this.completed; // loop
41
+ },
42
+ };